Home How does async-await allow for responsiveness in an application that uses an event loop, such as Windows Forms?
Reply: 1

How does async-await allow for responsiveness in an application that uses an event loop, such as Windows Forms?

user7127000
1#
user7127000 Published in 2017-12-07 16:34:18Z

As I understand, UI applications have a main thread that has a loop equivalent to

while(true)
{
   var e = events.Dequeue(); // get event from the queue
   e(); // run event
}

I'm confused about e() will be "unblocked" if it is at the top of an async-await chain. Let's say that e() eventually calls

public async Task WaitOneSecondAsync()
{
    await Task.Delay(1000);
    Console.WriteLine("Done waiting 1 second!");
}

Once the main thread hits await Task.Delay(1000); it won't "move on" to the next iteration of the while loop. So can you explain how this unblocks? Perhaps with a code sample?

Eric Lippert
2#
Eric Lippert Reply to 2017-12-07 17:22:09Z

Once the main thread hits await Task.Delay(1000); it won't "move on" to the next iteration of the while loop

Yes, it will.

Remember, Task.Delay is not Thread.Sleep. Sleep shuts down your thread and doesn't return until it's awake again. Task.Delay immediately returns a task which represents a delay.

So can you explain how this unblocks?

await has the following behaviour:

  • Check the task to see if it is completed. If yes, and it completed with an exception, throw the exception. If yes, and it completed normally, produce the value, if any, and continue executing normally.
  • The task is not completed. Make a delegate whose action is to start running this method again at the point of the await. (This is the tricky bit for the compiler to do; see any article on the details of the await codegen for an explanation.) Sign up that delegate as the continuation of the task, and return to your caller.
  • If this is the first await encountered in the method, the thing that is returned is a task representing the workflow of the method, so that the caller can in turn await it.

public async Task WaitOneSecondAsync()
{
    await Task.Delay(1000);
    Console.WriteLine("Done waiting 1 second!");
}

has these pseudocode semantics:

    Task thisTask = a new task;
    Action completion = () => { 
      Console.WriteLine("Done..."); 
      mark thisTask as complete
    };
    Task delayTask = Task.Delay(1000);
    if (delayTask has already completed normally)
    {
        completion();
        return thisTask; // Return a completed task.
    }
    else if (delayTask completed with an exception)
        throw the exception
    else
        assign completion as the completion of delayTask
        return thisTask;

See how that works? If the delay is not already complete then the method signs up a delegate as the completion of the delay and returns to the caller. If the caller is the message loop, then the message loop gets to run again.

What happens when the delay completes? It queues up a message that says to call its completion, and the completion runs at some point in the future.

Of course the real codegen is much, much more complicated than the little pseudocode I've shown here; that's just to get the idea across. The real codegen is complicated by the fact that exception handling is more difficult than just throwing the exception, and by a variety of optimizations that delay generating objects on the heap when possible.

You need to login account before you can post.

About| Privacy statement| Terms of Service| Advertising| Contact us| Help| Sitemap|
Processed in 0.332331 second(s) , Gzip On .

© 2016 Powered by mzan.com design MATCHINFO