Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does running several tasks asynchronously on UI thread using async/await work?

I've read (and used) async/await quite a lot for some time now but I still have one question I can't get an answer to. Say I have this code.

private async void workAsyncBtn_Click(object sender, EventArgs e)
{
    var myTask = _asyncAwaitExcamples.DoHeavyWorkAsync(5);
    await myTask;
    statusTextBox.Text += "\r\n DoHeavyWorkAsync message";
}

It's called from the UI thread and returned to the UI Thread. Therefor I am able to do UI-specific things in this method and after the await myTask. If I had used .ConfigureAwait(false) I would get a thread exception when doing statusTextBox.Text += "\r\n DoHeavyWorkAsync message"; since I would have telled myTask it's ok to take any available thread from the thread pool.

My question. As I understand it I never leave the UI thread in this case, still it's run asynchronously, the UI is still responsive and I can start several Tasks at the same time and therefor speed up my application. How can this work if we only use one thread?

Thanks!

EDIT for Sievajet

private async void workAsyncBtn_Click(object sender, EventArgs e)
{
    await DoAsync();
}

private async Task DoAsync()
{
    await Task.Delay(200);
    statusTextBox.Text += "Call to form";
    await Task.Delay(200);
}
like image 482
Andreas Avatar asked Jan 18 '15 11:01

Andreas


People also ask

Does async await run on separate thread?

An await expression in an async method doesn't block the current thread while the awaited task is running. Instead, the expression signs up the rest of the method as a continuation and returns control to the caller of the async method. The async and await keywords don't cause additional threads to be created.

Can async method run on the UI thread?

@pm100 The method they're calling is an asyncrhonous method that interacts with the UI, and as such needs to be run on the UI thread. It's incorrect to run it in a non-UI thread. It will never work if you do that. It needs to be run in the UI thread.

Can one async function have multiple awaits?

In order to run multiple async/await calls in parallel, all we need to do is add the calls to an array, and then pass that array as an argument to Promise. all() . Promise. all() will wait for all the provided async calls to be resolved before it carries on(see Conclusion for caveat).

Does async await block UI thread?

Though it creates a confusion, in reality async and await will not block the JavaScript main thread. Like mentioned above they are just syntactic sugars for promise chaining.


3 Answers

When UI thread calls await it starts the async operation and returns immediately. When the async operation completes, it notifies a thread from the thread pool but the internal implementation of async await dispatches the execution to the UI thread which will continue the execution of the code after the await.

The Dispatch is implemented by means of SynchronizationContext which in turn calls System.Windows.Forms.Control.BeginInvoke.

CLR via C# (4th Edition) (Developer Reference) 4th Edition by Jeffrey Richter page 749

Actually, Jeffrey worked with MS to implement the async/await inspired by his AsyncEnumerator

like image 103
user2418209 Avatar answered Oct 10 '22 12:10

user2418209


As I understand it I never leave the UI thread in this case, still it's run asynchronously, the UI is still responsive and I can start several Tasks at the same time and therefor speed up my application. How can this work if we only use one thread?

First, i'd recommend reading Stephan Clearys blog post - There is no thread.

In order to understand how its possible to run multiple units of work altogether, we need to grasp one important fact: async IO bound operations have (almost) nothing to do with threads.

How is that possible? well, if we drill deep down all the way to the operating system, we'll see that the calls to the device drivers - those which are in charge of doing operations such as network calls and writing to disk, were all implemented as naturally asynchronous, they don't occupy a thread while doing their work. That way, while the device driver is doing its thing, there need not be a thread. only once the device driver completes its execution, it will signal the operating system that it's done via an IOCP (I/O completion port), which will then execute the rest of the method call (this is done in .NET via the threadpool, which has dedicated IOCP threads).

Stephans blog post demonstrates this nicely:

Going down the async rabbit hole

Once the OS executes the DPC (Deferred Procedure Call) and queue the IRP (I/O Request Packet), it's work is essentially done until the device driver signals it back with the I'm done messages, which causes a whole chain of operations (described in the blog post) to execute, which eventually will end up with invoking your code.

Another thing to note is that .NET does some "magic" for us behind the scenes when using async-await pattern. There is a thing called "Synchronization Context" (you can find a rather lengthy explanation here). This sync context is whats in-charge of invoking the continuation (code after the first await) on the UI thread back again (in places where such context exists).

Edit:

It should be noted that the magic with the synchronization context happens for CPU bound operations as well (and actually for any awaitable object), so when you use a threadpool thread via Task.Run or Task.Factory.StartNew, this will work as well.

like image 11
Yuval Itzchakov Avatar answered Oct 10 '22 13:10

Yuval Itzchakov


The TaskParallelLibrary (TPL) uses a TaskScheduler which can be configured with TaskScheduler.FromCurrentSynchronizationContext to return to the SynchronizationContext like this :

textBox1.Text = "Start";
// The SynchronizationContext is captured here
Factory.StartNew( () => DoSomeAsyncWork() )
.ContinueWith( 
    () => 
    {
       // Back on the SynchronizationContext it came from            
        textBox1.Text = "End";
    },TaskScheduler.FromCurrentSynchronizationContext());

When an async method suspends at an await, by default it will capture the current SynchronizationContext and marshall the code after the await back on the SynchronizationContext it came from.

        textBox1.Text = "Start";

        // The SynchronizationContext is captured here

       /* The implementation of DoSomeAsyncWork depends how it runs, this could run on the threadpool pool 
          or it could be an 'I/O operation' or an 'Network operation' 
          which doesnt use the threadpool */
        await DoSomeAsyncWork(); 

        // Back on the SynchronizationContext it came from
        textBox1.Text = "End";

async and await example:

async Task MyMethodAsync()
{
  textBox1.Text = "Start";

  // The SynchronizationContext is captured here
  await Task.Run(() => { DoSomeAsyncWork(); }); // run on the threadPool

  // Back on the SynchronizationContext it came from
  textBox1.Text = "End";
}
like image 4
Sievajet Avatar answered Oct 10 '22 12:10

Sievajet