-5

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?

user7127000
  • 3,143
  • 6
  • 24
  • 41
  • `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. That's the whole point of asynchronous methods. They don't block; they return *immediately* (or close to it) and let the caller continue on doing whatever they want to do. – Servy Dec 07 '17 at 16:36
  • Possible duplicate of [How do yield and await implement flow of control in .NET?](https://stackoverflow.com/questions/42287737/how-do-yield-and-await-implement-flow-of-control-in-net) – John Wu Dec 07 '17 at 16:46
  • 2
    Yes. That's exactly what it will do. When the code hits await `Task.Delay`, it will configure the rest of `WaitOneSecondAsync` as a continuation, and then yield control to the caller, which is in the while loop. When `Task.Delay` finishes, the continuation (i.e. the rest of `WaitOneSecondAsync`) will itself be added to the events queue. – bornfromanegg Dec 07 '17 at 16:46
  • You can use a WaitOne to unblock. See msdn : https://msdn.microsoft.com/en-us/library/58195swd(v=vs.110).aspx – jdweng Dec 07 '17 at 17:14
  • Did you do *any* research on this question? there are lots of articles on how this works. – Eric Lippert Dec 07 '17 at 17:28

1 Answers1

6

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.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067