Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How Async and Await works

Tags:

c#

async-await

I am trying to understand how Async and Await works. How control travel in the program. Here is the code which I was trying to understand.

public async Task MyMethod()
{
    Task<int> longRunningTask = LongRunningOperation();
    //indeed you can do independent to the int result work here 
    MySynchronousMethod();
    //and now we call await on the task 
    int result = await longRunningTask;
    //use the result 
    Console.WriteLine(result);
}

public async Task<int> LongRunningOperation() // assume we return an int from this long running operation 
{
    await Task.Delay(5000); //5 seconds delay
    return 1;
}

private void Button_Click_3(object sender, RoutedEventArgs e)
{
     MyMethod();
}

When button click occur then MyMethod() will be called and from the MyMethod LongRunningOperation() will be called and it take 5 sec to complete. so my question is

  • What is the meaning of this line

    Task longRunningTask = LongRunningOperation();

  • what this does int result = await longRunningTask;

The above line could be committed and one line we can construct like

Task<int> longRunningTask = await LongRunningOperation();

or

int result = await longRunningTask;

Please can someone explain to me the above code which is not clear to me.

1) if the longRunningOperation hasn't finished and is still running, MyMethod() will return to its calling method, thus the main thread doesn't get blocked. When the longRunningOperation is done then a thread from the ThreadPool (can be any thread) will return to MyMethod() at its previous state and continue execution (in this case printing the result to the console).

A second case would be that the longRunningOperation has already finished its execution and the result is available. When reaching the await longRunningOperation the compiler knows that it has the result and will keep on executing code on the very same thread. (in this case printing result to console).

point 1 is not at all clear to me like the statement "if the longRunningOperation hasn't finished and is still running, MyMethod() will return to its calling method"

if possible explain the point one in more detail. thanks

like image 868
Thomas Avatar asked Mar 12 '14 10:03

Thomas


4 Answers

I've been taught about it in the following fashion, I found it to be quite a clear and concise explanation:

//this is pseudocode
async Method()
{
    code;
    code;
    await something; 
    moreCode;
}

When Method is invoked, it executes its contents (code; lines) up to await something;. At that point, something; is fired and the method ends like a return; was there.

something; does what it needs to and then returns.

When something; returns, execution gets back to Method and proceeds from the await onward, executing moreCode;

In a even more schematic fashion, here's what happens:

  1. Method is invoked
  2. code; is executed
  3. something; is executed, flow goes back to the point where Method was invoked
  4. execution goes on with what comes after the Method invocation
  5. when something; returns, flow returns inside Method
  6. moreCode; is executed and Method itself ends (yes, there could be something else await-ing on it too, and so on and so forth)
like image 176
Alex Avatar answered Oct 07 '22 16:10

Alex


I have an async intro on my blog that you may find helpful.

This code:

int result = await LongRunningOperation();

is essentially the same as this code:

Task<int> resultTask = LongRunningOperation();
int result = await resultTask;

So, yes, LongRunningOperation is invoked directly by that method.

When the await operator is passed an already-completed task, it will extract the result and continue executing the method (synchronously).

When the await operator is passed an incomplete task (e.g., the task returned by LongRunningOperation will not be complete), then by default await will capture the current context and return an incomplete task from the method.

Later, when the await task completes, the remainder of the method is scheduled to run in that context.

This "context" is SynchronizationContext.Current unless it is null, in which case it is TaskScheduler.Current. If you're running this in a Console app, then the context is usually the thread pool context, so the async method will resume executing on a thread pool thread. However, if you execute the same method on a UI thread, then the context is a UI context and the async method will resume executing on the UI thread.

like image 25
Stephen Cleary Avatar answered Oct 07 '22 16:10

Stephen Cleary


Behind the scenes C# compiler actually converts your code into a state machine. It generates a lot more code so that behind the scenes every time a await task or async action is completed, it'll continue execution from where it left off. In terms of your question, every time the async action has finished, the async method will be called back on the calling thread when you originally started the call to the async method. Eg it'll execute your code on the thread that you started on. So the async action will be run on a Task thread, then the result will be returned back on the thread you method was originally called on and keep executing.

Await will get the value from the Task or async action and "unbox" it from the task when the execution is returned. In this case it will automatically put it into the int value, so no need to store the Task<int>.

Your code has the problem where it await's on the LongRunningTask() you'd most likely just want to return the long task method without the async, then have your MyMethod perform the await.

int value = await LongWaitingTask()

Async Await and the Generated StateMachine

It's a requirement of async methods that you return a Task or void.

It's possible to change it so when you return back from executing the async task it will execute the remaining code on the thread the async task was performed on using the Task.ConfigureAwait method.

like image 26
Glenn Watson Avatar answered Oct 07 '22 15:10

Glenn Watson


It may be easier to think about it this way: Whenever you have an await, the compiler splits your method into 2: one part before the await and another part after it. The second part runs after the first one has finished successfully.

In your code, the first method will look like something roughly equivalent to this:

public async Task MyMethod()
{
    Task<int> longRunningTask = LongRunningOperation();
    MySynchronousMethod();

    longRunningTask.ContinueWith(t => part2(t.Result));
}

void part2(int result)
{
    Console.WriteLine(result);
}

Few important notes:

  1. It's obviously much more complex than this since it should support try-catch and others. Also, it doesn't really use the task
  2. Task is not actually being used directly. It's using the task's GetAwaiter() method and its API, or any other class with this method or extension method.
  3. If there are multiple awaits in a method it's being split multiple times.
  4. If MyMethod is being split, how does someone who awaits MyMethod knows when all parts are done? When your async method returns a Task, the compiler generates a special Task which tracks everything with a state machine.
like image 20
haimb Avatar answered Oct 07 '22 17:10

haimb