异步等待死锁

时间:2015-08-05 17:24:00

标签: c# asynchronous windows-phone-8.1 windows-phone async-await

我在Windows Phone 8.1上使用Accelerometer传感器。我必须从传感器的ReadingChanged回调访问用户界面。我还有一个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.
        }
    }
}

我不明白为什么会发生死锁。提前谢谢。

3 个答案:

答案 0 :(得分:3)

嗯,我很难过。

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线程以检索对窗口的引用,以及ReportInterval属性的一些奇怪的交互。 Accelerometer同步执行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++; });
    }
    

答案 1 :(得分:1)

根据@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.
        }
    }
}

这样就不会发生僵局。

答案 2 :(得分:1)

我在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; });
}