locked
Different thread after async/await in Windows Store App and Windows Phone App…? RRS feed

  • Question

  • Hi,

    I am porting a Windows Phone App to Windows 8 and I have problems trouble with async/await. There is a class that needs a while to load all its data. To not block the UI thread the loading is done in a separate task/thread. Once the loading is complete the UI thread needs to be updated:

        public class MyClass {
           public MyClass() {
              ...
              LoadData();
              ...
           }
    
           private async void LoadData() {
              Debug.WriteLine("StartLoad: " + Environment.CurrentManagedThreadId);
              await ReadDataAsync();
              Debug.WriteLine("EndLoad: " + Environment.CurrentManagedThreadId);
    
              UpdateUI();
           }
    
           private Task ReadDataAsync() {
              return Task.Run(() => {
                 Debug.WriteLine("Reading: " + Environment.CurrentManagedThreadId);
                 ...Read...
              });
           }
        } 



    On Windows Phone the Output is something like this:
    StartLoad: 1
    Reading: 4
    EndLoad: 1

    When I execute the same code in my Windows Store app the output is:
    StartLoad: 1
    Reading: 4
    EndLoad: 4

    While on Windows Phone the ThreadId is the same befor and after the await on Windows 8 the ThreadId after the await is NOT the same as before the await. Here it is the same Thread as in the background task. Because of the this UpdateUI() is executed in the wrong (non UI) thread and the app crashes...

    What is the reason for this different behaviour?
    Thursday, January 9, 2014 3:05 PM

Answers

  • The key to understanding your problem is understanding how exactly the Await/Async mechanism is implemented by the runtime. The compiler will generate a state machine to track, and execute your asynchronous call. On of the duties of the state machine is to marshall calls to/from threads, and it does this by using a SynchronizationContext. I won't go into the details of the SynchronizationContext as there is lots of information out there. Just understand that a SynchronizationContext is an object that is responsible for marshalling call across threads.  WPF and Windows Store apps install their own synchronization context implementations that use the Dispatcher to marshall calls back to the UI thread.

    Now understanding that there is a relationship between SynchronizationContexts and the compiler generated state machine, lets' now understand how the two work together.  Before you execute an asynchronous method, the compiler generated state machine will capture the current synchronization context (made available by SynchronizationContext.Current). Once the asynchronous method end, the state machine will use the captured SynchronizationContext to marshall the call back to the original destination. As I stated, Windows Store Apps and WPF use a Synchronization Context that marshals call back to the UI thread. The hosting environment is responsible for installing a SynchronizationContext, so it's important to understand WHEN the SynchronizationContext is made available. Windows Store apps install a SynchronziationContext BEFORE the OnLaunched method is called. A SynchronizationContext is NOT available during App construction

    With that info in mind, let's walk through your debugging example and let me show you what's happening


    App: 3 -> In the app constructor
    StartLoad: 3 -> Before your async method executes. Because this method was called from App constructor, no SyncContext is available, and thus non captured
    Reading: 4 -> On a seperate thread doing work
    EndLoad: 4 -> Async activity has completed. Since there was no SyncContext captured, then compiler has no choice but to continue work on the async thread

    OnLaunched: 3 -> You've entered the OnLaunched method
    StartLoad: 3 -> Before your async method executes. Because this method was called in the OnLaunched method, a SynchronizationContext IS available. The compiler generated state machine captures the context before invoking the async call
    Reading: 5 -> On a seperate thread doing work
    EndLoad: 3 -> Async activity has completed. Compiler can use the captured sync context to marshal the call. Since the SyncronizationContext that is installed by WinRT XAML libraries uses the Dispatcher, the call is marshalled back to the original UI thread, in your case thread 3

    Hope this helps

    Thursday, January 9, 2014 6:54 PM

All replies

  • Are you sure that the method is started by the UI Thread? 

    If the method is started by a background thread, then the resumption delegate can be scheduled on any thread.  If the method is started on the UIThread it will be resumed on the UIThread.

    See:

    "if you’re running on the UI thread and await a task, when the result of the task is ready, the rest of the asynchronous method will run back on the UI thread."

    Pause and Play with Await

    David


    David http://blogs.msdn.com/b/dbrowne/

    Thursday, January 9, 2014 3:30 PM
  • Are you sure that the method is started by the UI Thread?


    The constructor of MyClass (and thus LoadData() as well) is called right from the App() methode in the App class. This should be the UI thread, shouldn't it?

    Thursday, January 9, 2014 3:36 PM
  • Well, this is interesting:

    sealed partial class App : Application {
       public App() {            
          this.InitializeComponent();
          this.Suspending += OnSuspending;
    
          Debug.WriteLine("App: " + Environment.CurrentManagedThreadId);
    
          MyClass test = new MyClass();                 
       }
    
       protected override async void OnLaunched(LaunchActivatedEventArgs e) {
          Debug.WriteLine("OnLaunched: " + Environment.CurrentManagedThreadId);
    
          MyClass test = new MyClass();
       }
    }
    
    

    This creates:

    App: 3
    StartLoad: 3
    Reading: 4
    EndLoad: 4

    OnLaunched: 3
    StartLoad: 3
    Reading: 5
    EndLoad: 3

    Although in both cases the process is started from the same thread (3), the problem only shows up when the process is startet from the App() constructor but not when started from OnLaunched. Any idea why this is the case?





    • Proposed as answer by DPinTX Thursday, January 9, 2014 6:35 PM
    • Unproposed as answer by DPinTX Thursday, January 9, 2014 6:35 PM
    Thursday, January 9, 2014 4:24 PM
  • AFAIK, yes it should.  The VS template creates the MainPage from App.OnLaunched() instead of App(), so you might try to repro with a new project to troubleshoot.

    David


    David http://blogs.msdn.com/b/dbrowne/

    Thursday, January 9, 2014 4:24 PM
  • The key to understanding your problem is understanding how exactly the Await/Async mechanism is implemented by the runtime. The compiler will generate a state machine to track, and execute your asynchronous call. On of the duties of the state machine is to marshall calls to/from threads, and it does this by using a SynchronizationContext. I won't go into the details of the SynchronizationContext as there is lots of information out there. Just understand that a SynchronizationContext is an object that is responsible for marshalling call across threads.  WPF and Windows Store apps install their own synchronization context implementations that use the Dispatcher to marshall calls back to the UI thread.

    Now understanding that there is a relationship between SynchronizationContexts and the compiler generated state machine, lets' now understand how the two work together.  Before you execute an asynchronous method, the compiler generated state machine will capture the current synchronization context (made available by SynchronizationContext.Current). Once the asynchronous method end, the state machine will use the captured SynchronizationContext to marshall the call back to the original destination. As I stated, Windows Store Apps and WPF use a Synchronization Context that marshals call back to the UI thread. The hosting environment is responsible for installing a SynchronizationContext, so it's important to understand WHEN the SynchronizationContext is made available. Windows Store apps install a SynchronziationContext BEFORE the OnLaunched method is called. A SynchronizationContext is NOT available during App construction

    With that info in mind, let's walk through your debugging example and let me show you what's happening


    App: 3 -> In the app constructor
    StartLoad: 3 -> Before your async method executes. Because this method was called from App constructor, no SyncContext is available, and thus non captured
    Reading: 4 -> On a seperate thread doing work
    EndLoad: 4 -> Async activity has completed. Since there was no SyncContext captured, then compiler has no choice but to continue work on the async thread

    OnLaunched: 3 -> You've entered the OnLaunched method
    StartLoad: 3 -> Before your async method executes. Because this method was called in the OnLaunched method, a SynchronizationContext IS available. The compiler generated state machine captures the context before invoking the async call
    Reading: 5 -> On a seperate thread doing work
    EndLoad: 3 -> Async activity has completed. Compiler can use the captured sync context to marshal the call. Since the SyncronizationContext that is installed by WinRT XAML libraries uses the Dispatcher, the call is marshalled back to the original UI thread, in your case thread 3

    Hope this helps

    Thursday, January 9, 2014 6:54 PM
  • Hi,

    thank you very much for this excellent explanation. This makes everything much clearer. Is the OnLaunched then the best place to setup custom classes? Or is there any methode which is executed before OnLaunched and where eveything is setup up correctly already?

    Friday, January 10, 2014 6:34 AM