Async Waiting for deadlock

I am working with a sensor Accelerometer

on Windows Phone 8.1. I have to access the UI from the ReadingChanged

sensor callback . I also have DispatcherTimer

one that updates the ReportInterval

sensor every two seconds . The program blocks when the timer fires and tries to set the ReportInterval of the accelerometer. The example below is a minimal 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 is happening. Thank you in advance.

+3


source to share


3 answers


Well, I'm stumped.

Dispatcher.RunAsync

should not be deadlocked. So in order to figure out exactly where the problem is, I rewrote your code to a few 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 var window = view.CoreWindow;

. It's hard to explain why without seeing the WinRT source code, I'm guessing that there is some strange interaction between WinRT that needs to be switched to the UI thread to get a reference to the window, and the ReportInterval

Accelerometer property that executes the ReadingChanged

event synchronously .

From there I can come up with several solutions:

  • Get the dispatcher in another way:

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

    Of course, whether this is possible or not depends on your actual code.

  • Rewrite your code to use Timer

    instead DispatcherTimer

    . I understand that you need to use the UI thread to get the value of the textbox (or something like that), but if you are using data binding (with or without the MVVM pattern) then you should be able to read the value of the associated property from any stream

  • Change ReportInterval

    in a different thread. Feels like really hacking.

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

+3


source


As per @KooKiz explanation and @StephenCleary comment, I found another possible solution. Since we realized that the problem is here:

var window = view.CoreWindow;

      

we can cache the dispatcher by storing it as an instance variable. In doing so, we avoid accessing it at the same time as 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.
        }
    }
}

      

Thus, there is no deadlock.

+1


source


I created this extension after having blocking issues on WinRT and it solved my problems (so far):

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

      

Using

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

      

+1


source







All Articles