C# Async Await, Simply

Henrick Tissink on September 26, 2019

One at a time Code is executed to achieve a purpose. Usually it's run as a series of steps, eventually leading to a final result. Each o... [Read Full]
markdown guide
 

I understand why you are using Thread.Sleep (to mimic doing some CPU intensive work) but I fear it will also give beginners the wrong impression about using Thread.Sleep in tasks to delay work. I think instead of ...

public static async Task WorldDomination()
{
    await Task.Run(() =>
    {
        Thread.Sleep(6000);
        Console.WriteLine("World Domination Achieved!");
    });
}

If you need to delay before doing something in a task ...

public static async Task WorldDomination()
{
    await Task.Delay(6000);
    Console.WriteLine("World Domination Achieved!");
}

If you Thread.Sleep in many running tasks, performance may suffer. If you do it to excess, work may have to be queued waiting for all the sleeping threads to wake and finish executing their work. At the very worst you could get out of memory exceptions.

Like I said I understand why you chose to use Thread.Sleep for your example but beginners shouldn't think that using Thread.Sleep in tasks is good practice.

Helping others understand async/await is no mean feat. Good on you for giving it a go.

 

You make a good point. Awaiting a Task.Delay is much better - Thread.Sleep was just used for the illustration. I'll fix it when I get the chance :)

 

If you are worried about beginners than explain them !

 

If you starting a new Task.Run or having a Task in a method without something to do after it, you don't need to async/await it in the method.

Example, instead of:

public static async Task<int> MakeOneMillionDollars()
{
    return await Task.Run(() =>
    {
        Console.WriteLine("Million Dollars Made!");
                return 1000000;
    });
}

you can just write this:

public static Task<int> MakeOneMillionDollars()
{
    return Task.Run(() =>
    {
        Console.WriteLine("Million Dollars Made!");
        return 1000000;
    });
}

This removes some async/await overhead :)

Also as already mentioned Task.Delay instead of Thread.Sleep

 

Good point, but also it should always be taken with great care in production scenarios that utilize try/catch in similar code as now you are risking of not having the exception unwrapped in the MakeOneMillionDollars due to not awaiting it here explicitly. But the perf tip is on point as this code will produce one compiler generated type less with its following state machine.

 

I quote "Awaiting asynchronous code, two things happen. A thread is taken from the thread pool and allocated to the asynchronous operation".

That's just wrong and a big misunderstanding of async/await. Asynchrony doesn't need to get threads from the thread pool or otherwise.

In your case it just happens because you used Task.Run but that's a edge case and actually not a good practice with async/await (Task.Run is more suited with TPL but you can use async/await when you combine with other asynchronous calls). Most asynchronous code never consumes threads.

See blog.stephencleary.com/2013/11/the...

That said I did enjoy reading the article. But that's because I'm evil!

 

Hi, I appreciate your comment - and you are partially correct.

It's up to the scheduler to determine if a new thread is needed. It may or may not spin up a new thread. I'll clarify it when I have a chance.

 

It is a bit more than that actually. Your example demonstrates parallel computation not asynchrony. Task.Run never creates asynchronous code, whether the scheduler decides to run it immediatly on the same thread, or to schedule it on a worker.

It is understandable to be confused since most of us (me included) started to use tasks when async/await got out. But the Task type was originally created for parallel computing with the TPL (hence why it is in a System.Threading sub-namespace). Asynchronous task (also called future or promise in other languages) should have used a different type but the .Net team decided to reuse Task for that purpose. It does lead to a unified model where we can combine both, but it also adds to the confusion.

I think a better example would be to use sockets or file asynchronous reading/writing, which doesn't involve any threads.

Thanks for the explanation (and the link) Nicolas :) you really taught me something valuable today.

 

Nice one, and perhaps it's just me, but I find your lack of faith distur... No, wait, I find your usage of animated gifs disturbing. IMHO, There's way too many of them in an otherwise really good and simple (and funny! that counts!) introduction to async programming.

 

Thanks for the feedback - I always appreciate constructive criticism. I'll try and find a better balance for the gifs.

 
 

I loved the article!!

I've never understood async/await until now, and had to extract all those methods to another one and had it called from Main using .Wait(). I was like "Damn, that is good use and explanation" while laughing a lot

 

This is probably the first time I've fully understood async and await and the benefits it provides all while being an interesting and fun.

Thank you.

 

Thanks for the compliment Rob :) I really appreciate it.

 
 
 

There are so many misconceptions and bad habits in this article. Please update it with better code examples and clear up the misconceptions like the conflation between async and threads.

 

Brilliant work mate, good to see some genuinely good content mixed in with humour. Thanks for this.

 
code of conduct - report abuse