Rijk
Rijk

Reputation: 11301

How is async with await different from a synchronous call?

I was reading about asynchronous function calls on Asynchronous Programming with Async and Await.

At the first example, they do this, which I get:

Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");

// You can do work here that doesn't rely on the string from GetStringAsync.
DoIndependentWork();

string urlContents = await getStringTask;

But then they explain that if there's not any work to be done in the mean time, you can just do it like this:

string urlContents = await client.GetStringAsync();

From what I understand, the await keyword will suspend the code flow until the function returns. So how is this different from the synchronous call below?

string urlContents = client.GetString();

Upvotes: 48

Views: 21376

Answers (2)

Adi Lester
Adi Lester

Reputation: 25211

Calling await client.GetStringAsync() yields the execution to the calling method, which means it won't wait for the method to finish executing, and thus won't block the thread. Once it's done executing in the background, the method will continue from where it stopped.

If you just call client.GetString(), the thread's execution won't continue until this method finished executing, which will block the thread and may cause the UI to become unresponsive.

Example:

public void MainFunc()
{
    InnerFunc();
    Console.WriteLine("InnerFunc finished");
}

public async Task InnerFunc()
{
    // This causes InnerFunc to return execution to MainFunc,
    // which will display "InnerFunc finished" immediately.
    string urlContents = await client.GetStringAsync();

    // Do stuff with urlContents
}

public void InnerFunc()
{
    // "InnerFunc finished" will only be displayed when InnerFunc returns,
    // which may take a while since GetString is a costly call.
    string urlContents = client.GetString();

    // Do stuff with urlContents
}

Upvotes: 29

YK1
YK1

Reputation: 7622

From what I understand, the await keyword will suspend the code flow until the function returns

Well, Yes and No.

  • Yes, because code flow does stop in a sense.
  • No, because the thread executing this code flow does not block. (The synchronous call client.GetString() will block the thread).

In fact, it will return to its calling method. To understand what it means by return to its calling method, you can read about another C# compiler magic - the yield return statement.

Iterator blocks with yield return will break the method into a state machine - where code after the yield return statement will execute only after MoveNext() is called on the enumerator. (See this and this).

Now, async/await mechanism is also based on similar state machine (however, its much more complicated than the yield return state machine).

To simplify matters, lets consider a simple async method:

public async Task MyMethodAsync()
{
    // code block 1 - code before await

    // await stateement
    var r = await SomeAwaitableMethodAsync();

    // code block 2 - code after await
}
  • When you mark a method with async identifier you tell the compiler to break the method into a state machine and that you are going to await inside this method.
  • Lets say code is running on a thread Thread1 and your code calls this MyMethodAsync(). Then code block 1 will synchronously run on the same thread.
  • SomeAwaitableMethodAsync() will also be called synchronously - but lets say that method starts a new asynchronous operation and returns a Task.
  • This is when await comes into picture. It will return the code flow back to its caller and the thread Thread1 is free to run callers code. What happens then in calling method depends on whether calling method awaits on MyMethodAsync() or does something else - but important thing is Thread1 is not blocked.
  • Now rest of await's magic - When the Task returned by SomeAwaitableMethodAsync() eventually completes, the code block 2 is Scheduled to run.
  • async/await is built on the Task parallel library - so, this Scheduling is done over TPL.
  • Now the thing is that this code block 2 may not be scheduled over the same thread Thread1 unless it had an active SynchronizationContext with thread affinity (like WPF/WinForms UI thread). await is SynchronizationContext aware, so, code block 2 is scheduled over same SynchronizationContext, if any, when the MyMethodAsync() was called. If there was no active SynchronizationContext, then with all possibility, code block 2 will run over some different thread.

Lastly, I will say that since async/await is based on state machine created by compiler, like yield return, it shares some of the shortcomings - for example, you cannot await inside a finally block.

I hope this clears your doubts.

Upvotes: 18

Related Questions