簡體   English   中英

異步等待死鎖

[英]Async Await deadlock

我在Windows Phone 8.1上使用Accelerometer傳感器。 我必須從傳感器的ReadingChanged回調中訪問UI。 我還有一個DispatcherTimer ,它每兩秒更新一次傳感器的ReportInterval 當計時器觸發並嘗試設置Accelerometer的ReportInterval時,程序會阻塞。 下面的示例是再現錯誤的最小可執行示例。

namespace TryAccelerometer
{        
    public sealed partial class MainPage : Page
    {
        private Accelerometer acc;
        private DispatcherTimer timer;                
        private int numberAcc = 0;
        private int numberTimer = 0;

        public MainPage()
        {
            this.InitializeComponent();
            this.NavigationCacheMode = NavigationCacheMode.Required;

            acc = Accelerometer.GetDefault();                                    
            acc.ReadingChanged += acc_ReadingChanged;

            timer = new DispatcherTimer();
            timer.Interval = TimeSpan.FromSeconds(2);
            timer.Tick += timer_Tick;
            timer.Start();            
        }

        async void acc_ReadingChanged(Accelerometer sender, AccelerometerReadingChangedEventArgs args)
        {            
            await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
            {
                //HERE I WILL HAVE TO ACCESS THE UI, BUT FOR SAKE OF SIMPLICITY I WROTE AN INCREMENT
                numberAcc++;
            });
        }

        void timer_Tick(object sender, object e)
        {
            numberTimer++;            
            //PUT A BREAKPOINT HERE BELOW AND SEE THAT THE PROGRAM BLOCKS
            acc.ReportInterval = acc.ReportInterval++;
        }
        /// <summary>
        /// Invoked when this page is about to be displayed in a Frame.
        /// </summary>
        /// <param name="e">Event data that describes how this page was reached.
        /// This parameter is typically used to configure the page.</param>
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            // TODO: Prepare page for display here.

            // TODO: If your application contains multiple pages, ensure that you are
            // handling the hardware Back button by registering for the
            // Windows.Phone.UI.Input.HardwareButtons.BackPressed event.
            // If you are using the NavigationHelper provided by some templates,
            // this event is handled for you.
        }
    }
}

我不明白為什么會發生死鎖。 先感謝您。

好吧,我很難過。

Dispatcher.RunAsync不應該導致死鎖。 因此,為了找出問題的確切位置,我在多行上重寫了您的代碼:

async void acc_ReadingChanged(Accelerometer sender, AccelerometerReadingChangedEventArgs args)
{
    var view = Windows.ApplicationModel.Core.CoreApplication.MainView;

    var window = view.CoreWindow;

    var dispatcher = window.Dispatcher;

    await dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => { numberAcc++; });
}

真正的罪魁禍首是var window = view.CoreWindow; 很難解釋為什么沒有看到WinRT源代碼,我想WinRT之間需要切換到UI線程以檢索對窗口的引用,以及Accelerometer的ReportInterval屬性同步執行ReadingChanged事件之間存在一些奇怪的交互。

從那里,我可以想到一些解決方案:

  1. 以另一種方式檢索調度程序:

     async void acc_ReadingChanged(Accelerometer sender, AccelerometerReadingChangedEventArgs args) { await this.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => { numberAcc++; }); } 

    當然,是否可能取決於您的實際代碼。

  2. 重寫您的代碼以使用Timer而不是DispatcherTimer 我知道你需要使用UI線程來檢索文本框的值(或類似的東西),但如果你使用數據綁定(有或沒有MVVM模式),那么你應該能夠訪問讀取的值任何線程的綁定屬性

  3. 在另一個線程中更改ReportInterval 雖然感覺真的很邪惡。

     void timer_Tick(object sender, object e) { numberTimer++; Task.Run(() => { acc.ReportInterval = acc.ReportInterval++; }); } 

根據@KooKiz的解釋和@StephenCleary評論,我找到了另一種可能的解決方案。 既然我們已經明白問題在這里:

var window = view.CoreWindow;

我們可以緩存調度程序將其保存為實例變量。 這樣做,我們避免在計時器的同時訪問它:

namespace TryAccelerometer
{        
    public sealed partial class MainPage : Page
    {
        private Accelerometer acc;
        private DispatcherTimer timer;                
        private int numberAcc = 0;
        private int numberTimer = 0;
        private CoreDispatcher dispatcher;

        public MainPage()
        {
            this.InitializeComponent();
            this.NavigationCacheMode = NavigationCacheMode.Required;

            dispatcher = Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher;

            acc = Accelerometer.GetDefault();                                    
            acc.ReadingChanged += acc_ReadingChanged;

            timer = new DispatcherTimer();
            timer.Interval = TimeSpan.FromSeconds(2);
            timer.Tick += timer_Tick;
            timer.Start();            
        }

        async void acc_ReadingChanged(Accelerometer sender, AccelerometerReadingChangedEventArgs args)
        {            
            await dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
            {
                numberAcc++;
            });
        }

        void timer_Tick(object sender, object e)
        {
            numberTimer++;            
            acc.ReportInterval = acc.ReportInterval++;
            //acc.ReadingChanged -= acc_ReadingChanged;
        }
        /// <summary>
        /// Invoked when this page is about to be displayed in a Frame.
        /// </summary>
        /// <param name="e">Event data that describes how this page was reached.
        /// This parameter is typically used to configure the page.</param>
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            // TODO: Prepare page for display here.

            // TODO: If your application contains multiple pages, ensure that you are
            // handling the hardware Back button by registering for the
            // Windows.Phone.UI.Input.HardwareButtons.BackPressed event.
            // If you are using the NavigationHelper provided by some templates,
            // this event is handled for you.
        }
    }
}

這樣就不會發生死鎖。

我在WinRT上遇到死鎖問題后創建了這個擴展,它解決了我的問題(到目前為止):

using global::Windows.ApplicationModel.Core;
using global::Windows.UI.Core;

public static class UIThread
{
    private static readonly CoreDispatcher Dispatcher;

    static DispatcherExt()
    {
        Dispatcher = CoreApplication.MainView.CoreWindow.Dispatcher;
    }

    public static async Task Run(DispatchedHandler handler)
    {
        await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, handler);
    }
}

用法

public async Task Foo()
{
    await UIThread.Run(() => { var test = 0; });
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM