1
votes

I've read about asynchronous programming in C#, but I still do not fully understand how continuation of async method is executed. From my understanding, Asynchronous programming is not about multithreading. We can run async method on UI Thread, and it later continues on that UI Thread (while not blocking and continuing to respond to other messages from message loop).

This is basic message loop most GUI apps have:

while (1)
{
    bRet = GetMessage(&msg, NULL, 0, 0);

    if (bRet > 0)  // (bRet > 0 indicates a message that must be processed.)
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    ...

DispatchMessage() calls UI event handlers. And the code inside the event handlers should not block the main thread. For that reason if we want to, i.e. create a button that loads a heavy data from disk, we can use async method like this: (simplified pseudo code)

public async Task ButtonClicked()
{
  loadingBar.Show();
  await AsyncLoadData();
  loadingBar.Hide();
}

When execution reaches await AsyncLoadData(); line, it stores the context and returns the Task object. DispatchMessage() finishes and message loop repeatedly comes to the bRet = GetMessage(&msg, NULL, 0, 0); line.

So my question is, how the rest code is executed? Is finished async operation triggers a new message, that is then handled by DispatchMessage() again? Or the message loop has another method (after dispatch), that checks for finished async operations?

2
You may find this interesting: Dissecting the async methods in C# - Theodor Zoulias

2 Answers

3
votes

So my question is, how the rest code is executed? Is finished async operation triggers a new message, that is then handled by DispatchMessage() again? Or the message loop has another method (after dispatch), that checks for finished async operations?

await by default will capture a "context" and use that to resume the execution of the method. This "context" is SynchronizationContext.Current, falling back on TaskScheduler.Current. UI apps provide a SynchronizationContext, e.g., WindowsFormsSynchronizationContext or DispatcherSynchronizationContext. When the await completes, it schedules the continuation of the method onto that context (in this case, onto the SynchronizationContext).

For WinForms, the syncctx uses Control.BeginInvoke, which will post a Win32 message which is handled by the WinProc.

For WPF, the syncctx posts to its Dispatcher, which adds the callback to the dispatch queue. This queue is also processed by a Win32 WinProc loop.

0
votes

Alex Davies wrote an excellent book on this, it is called "Async in C# 5", I strongly recommend reading it. I can't tell low level details behind this, but at the high level CLR will create something like this:

void __buttoncliked_remaining_code_1(...) { 
    loadingBar.Hide(); 
} 

So, a specific event will be triggered indicating that async job has completed. Then __buttoncliked_remaining_code_1() will be executed, sinchronously, just like any regular C# function. CLR will use for that any thread, but most likely it will reuse the one that encountered await keyword, which in your case might be GUI thread.