Understanding Async/Await in C#

Understanding async / await in C#

I recommend you start out with my intro to async/await and follow-up with the official Microsoft documentation on TAP.

As I mention in my intro blog post, there are several Task members that are holdovers from the TPL and have no use in pure async code. new Task and Task.Start should be replaced with Task.Run (or TaskFactory.StartNew). Similarly, Thread.Sleep should be replaced with Task.Delay.

Finally, I recommend that you do not use Task.WaitAll; your Console app should just Wait on a single Task which uses Task.WhenAll. With all these changes, your code would look like:

class Program
{
static void Main(string[] args)
{
MainAsync().Wait();
}

public static async Task MainAsync()
{
Task task1 = Task1();
Task task2 = Task2();

await Task.WhenAll(task1, task2);

Debug.WriteLine("Finished main method");
}

public static async Task Task1()
{
await Task.Delay(5000);
Debug.WriteLine("Finished Task1");
}

public static async Task Task2()
{
await Task.Delay(10000);
Debug.WriteLine("Finished Task2");
}
}

How and when to use ‘async’ and ‘await’

When using async and await the compiler generates a state machine in the background.

Here's an example on which I hope I can explain some of the high-level details that are going on:

public async Task MyMethodAsync()
{
Task<int> longRunningTask = LongRunningOperationAsync();
// independent work which doesn't need the result of LongRunningOperationAsync can be done here

//and now we call await on the task
int result = await longRunningTask;
//use the result
Console.WriteLine(result);
}

public async Task<int> LongRunningOperationAsync() // assume we return an int from this long running operation
{
await Task.Delay(1000); // 1 second delay
return 1;
}

OK, so what happens here:

  1. Task<int> longRunningTask = LongRunningOperationAsync(); starts executing LongRunningOperation

  2. Independent work is done on let's assume the Main Thread (Thread ID = 1) then await longRunningTask is reached.

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

A second case would be that the longRunningTask has already finished its execution and the result is available. When reaching the await longRunningTask we already have the result so the code will continue executing on the very same thread. (in this case printing result to console). Of course this is not the case for the above example, where there's a Task.Delay(1000) involved.

can anyone help me understand await/async implementation in c#

When compiling your code, you should have received warnings that gave you a hint what was wrong:

warning CS4014: Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

An async method will run synchronously until it reaches the first await expression where the value it's awaiting isn't already completed.

In your case, all your async methods return completed tasks - Method1Async and Method2Async will simply run synchronously, then return a completed task, which means that CallMethodAsync will await those already-completed tasks, and itself complete synchronously.

If you add something like await Task.Yield() in Method1Async and Method2Async, then you'll see genuine asynchronous behavior: those methods will return incomplete tasks (when they reach Task.Yield) which means CallMethodAsync itself will return an incomplete tasks, etc.

understanding Async and Await in C#

Asynchronous is not concurrent. Each call to await will prevent the code following from being executed until the Task has completed. In this case, you can create concurrency by changing your example to look like this

class Program
{
static void Main()
{
new Content().Print().Wait();
Console.Read();
}
}

class Content
{

public async Task<string> Delay1()
{
await Task.Delay(5000);
return "hello";
}
public async Task<string> Delay2()
{
await Task.Delay(5000);
return "hello";
}
public async Task<string> Delay3()
{
await Task.Delay(5000);
return "hello";
}

public async Task Print()
{
var tasks = new[] {Delay1(), Delay2(), Delay3()};
await Task.WhenAll(tasks);
foreach(var result in tasks.Select(x => x.Result))
{
Console.WriteLine(result);
}
}
}

You can start three Tasks independently of one another and store them in a collection. Then, you can call await Task.WhenAll to block execution until all of those tasks have completed. Afterwards, you can loop through the results and use them however you want.

C# - understanding async/await

Because you're calling await on HashAsync the control will be yielded to the caller, which is this case is .NET Framework itself, which called your Main method.

I think the easiest way to see how this works would be to assign the Task returned from HashAsync to a variable but not await it until after the Console.WriteLine:

private static async Task Main(string[] args)
{
Task<string> resultTask = HashAsync(Guid.NewGuid().ToByteArray(), 4);

Console.WriteLine("Calculating hash...");'

string result = await resultTask;

Console.WriteLine(result);

Console.Read();
}

With this change, once you call into HashAsync it will push work to the background using Task.Run and return you a Task to observe progress of that work. But because you're not awaiting it Main method will continue executing and Calculating hash... will be printed. Only once you call await resultTask control will be returned to whoever called Main and execution will be suspended.

Trouble understanding async and await


Isn't adding a suspension point forcing the method to act synchronously, i.e. finish the task marked by the await before moving on.

No, the word you're thinking of is "sequential", not "synchronous". await results in asynchronous sequential code. "Sequential" meaning "one at a time"; "synchronous" meaning "blocking until completed".

how do you call an async method normally?

Using await.

How would you 'escape' this wrapping of async/await to run the method?

Ideally, you don't. You go async all the way. Modern frameworks (including ASP.NET MVC, Azure Functions / WebJobs, NUnit / xUnit / MSTest, etc) all allow you to have entry points that return Task. Less-modern frameworks (including WinForms, WPF, Xamarin Forms, ASP.NET WebForms, etc) all allow async void entry points.

So, ideally you do not call asynchronous code from synchronous code. This makes sense if you think about what asynchronous code is: the entire point of it is to not block the calling thread, so if you block the calling thread on asynchronous code, then you lose all the benefits of asynchronous code in the first place.

That said, there are rare situations where you do need to treat the code synchronously. E.g., if you are in the middle of a transition to async, or if you are constrained by a library/framework that is forcing your code to be synchronous and won't work with async void. In that case, you can employ one of the hacks in my article on brownfield async.

Async/Await in Javascript vs C#

I am not familiar with JavaScript but this statement:

Async/await makes your code look synchronous, and in a way it makes it
behave more synchronously. The await keyword blocks execution of all
the code that follows it until the promise fulfills, exactly as it
would with a synchronous operation.

Sounds pretty much applicable to C# async/await. For both cases your code looks like you execute it synchronously and your execution is sequential. Because in C# when you have code like this:

// ...
await FooAsync();
Console.WriteLine("Await has returned the execution");

It seems as if your execution thread were running FooAsync, and then, the same thread is calling Console.WriteLine. Whereas in reality when the execution thread hits await, it does lots of things behind the scene. Here's a good article about it. But in most of the cases,

When the await keyword is applied, it suspends the calling method and
yields control back to its caller until the awaited task is complete.

The thread that were executing your code will go about his business. And then proceed with Console.WriteLine when FooAsync is complete by another (or the same) thread.
This behavior is enormously helpful when you work with UI threads like in WPF or WinForms applications.
For example, FooAsync is a very heavy method. It does lots of calculations and takes a lot of time to complete. But you're running your code on UI and when a user hits a button, the underlying code is executed by the UI thread. So if you'll be running and waiting FooAsync synchronously like this:

FooAsync().Result;

Your UI would be "freezed" and the user would demise you.
So when you go

await FooAsync();

UI thread "asks" TaskScheduler to run FooAsync by whatever available thread. After the Task is completed, TaskScheduler tries to execute next line:

Console.WriteLine("Await has returned the execution");

by the UI thread again,

exactly as it would with a synchronous operation.

Trying to understand async and await

When an await is encountered, progress is suspended and control is yielded back to the calling method. The awaited operation neither blocks nor is executed (until a later time).

I recommend reviewing this helpful msdoc example.

Understanding Async and await in .Net

It seems you misunderstand what async and await does. Common misconception.

Async Await is NOT concurrency.

I suspect that you expect that GetIntAsync and GetstringAsync should be run in parallel.

In Rx Marble diagrams meaning:

---o
-------o

Where as you found:

---o
------o

Lets break down what you ACTUALLY wrote.

var i =  await GetIntAsync(10);
var j = await GetstringAsync("abc");

Which can be expanded to:

var taskI = GetIntAsync(10);
var i = await taskI;
var taskJ = GetstringAsync("abc");
var j = await taskJ;

Notice that you start taskI, wait for its completion, THEN you start taskJ, THEN wait for that to complete.

Where as example 2 (after reformatting):

var taskI =   GetIntAsync(10);
var taskJ = GetstringAsync("abc");
var i = await taskI;
var j = await taskJ;

Notice how ordering has changed.

FYI: You can also do

https://stackoverflow.com/a/40938652/1808494

public static class TaskEx
{
public static async Task<(T1, T2)> WhenAll<T1, T2>(Task<T1> task1, Task<T2> task2)
{
return (await task1, await task2);
}
}

var (i, j) = await TaskEx.WhenAll(
GetIntAsync(10),
GetstringAsync("abc"));

Understanding async / await and Task.Run()

It's as simple as you not awaiting the Task.Run, so the exception gets eaten and not returned to the call site of Task.Run.

Add "await" in front of the Task.Run, and you'll get the exception.

This will not crash your application:

private void button1_Click(object sender, EventArgs e)
{
Task.Run(() => { throw new Exception("Hello");});
}

This however will crash your application:

private async void button1_Click(object sender, EventArgs e)
{
await Task.Run(() => { throw new Exception("Hello");});
}


Related Topics



Leave a reply



Submit