简体   繁体   English

异步等待死锁

[英]Async Await deadlock

I'm working with the Accelerometer sensor on Windows Phone 8.1. 我在Windows Phone 8.1上使用Accelerometer传感器。 I must access the UI from the ReadingChanged callback of the sensor. 我必须从传感器的ReadingChanged回调中访问UI。 I also have a DispatcherTimer that, every two seconds, updates the ReportInterval of the sensor. 我还有一个DispatcherTimer ,它每两秒更新一次传感器的ReportInterval The program blocks when the timer fires and try to set the ReportInterval of the Accelerometer. 当计时器触发并尝试设置Accelerometer的ReportInterval时,程序会阻塞。 The example here below is a minimum executable example that reproduces the error. 下面的示例是再现错误的最小可执行示例。

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.
        }
    }
}

I don't understand why the deadlock happens. 我不明白为什么会发生死锁。 Thank you in advance. 先感谢您。

Well I'm stumped. 好吧,我很难过。

Dispatcher.RunAsync shouldn't possibly cause a deadlock. Dispatcher.RunAsync不应该导致死锁。 Therefore, to find out where exactly the issue is, I rewrote your code on multiple lines: 因此,为了找出问题的确切位置,我在多行上重写了您的代码:

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++; });
}

The real culprit is var window = view.CoreWindow; 真正的罪魁祸首是var window = view.CoreWindow; . It's hard to explain why without seeing the WinRT source code, I guess there is some weird interaction between WinRT needing to switch to the UI thread to retrieve the reference to the window, and the ReportInterval property of the Accelerometer executing synchronously the ReadingChanged event. 很难解释为什么没有看到WinRT源代码,我想WinRT之间需要切换到UI线程以检索对窗口的引用,以及Accelerometer的ReportInterval属性同步执行ReadingChanged事件之间存在一些奇怪的交互。

From there, I can think of a few solutions: 从那里,我可以想到一些解决方案:

  1. Retrieve the dispatcher another way: 以另一种方式检索调度程序:

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

    Of course, whether it's possible or not depends on your actual code. 当然,是否可能取决于您的实际代码。

  2. Rewrite your code to use a Timer instead of a DispatcherTimer . 重写您的代码以使用Timer而不是DispatcherTimer I understand that you need to use the UI thread to retrieve the value of a textbox (or something like that), but if you use databinding (with or without the MVVM pattern), then you should be able to access the read the value of the bound property from any thread 我知道你需要使用UI线程来检索文本框的值(或类似的东西),但如果你使用数据绑定(有或没有MVVM模式),那么你应该能够访问读取的值任何线程的绑定属性

  3. Change the ReportInterval in another thread. 在另一个线程中更改ReportInterval Feels really hackish though. 虽然感觉真的很邪恶。

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

According to the @KooKiz explanation and the @StephenCleary comment I found another possible solution. 根据@KooKiz的解释和@StephenCleary评论,我找到了另一种可能的解决方案。 Since we have understood that the problem is here: 既然我们已经明白问题在这里:

var window = view.CoreWindow;

we can cache the dispatcher saving it as an instance variable. 我们可以缓存调度程序将其保存为实例变量。 Doing so, we avoid to access it at the same time of the timer: 这样做,我们避免在计时器的同时访问它:

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.
        }
    }
}

This way the deadlock doesn't happen. 这样就不会发生死锁。

I created this extension after having deadlock issues on WinRT, and it solved my problems (so far): 我在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);
    }
}

Usage 用法

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM