DEV Community

collen brecht
collen brecht

Posted on • Edited on

Explaining async await

Async await

Why

I/O or CPU intensive operations

How

Async and await keywords

Example

Doing the dishes is tedious work. When you first start doing dishes, it is synchronous: you are at the sink doing the dishes. You can’t do anything else. If only there was something that could do it for you, so you could be doing something else.
In comes: the dishwasher. When you have put away the dishes and filled up the soap, you tell it to do the dishes for you. In the mean time, you can go read a book or play a game while waiting. When it’s ready, it will make a sound so you can put away the clean dishes.

The Code

you can find the code on GitHub:

GitHub logo collenbrecht / AsyncAwaitExample

trying to explain Async Await using doing dishes example

AsyncAwaitExample

trying to explain Async Await using doing dishes example




First, there is the class dishes that contains the work to be done. I’ve chosen something that takes some time: hashing a string:

public class Dishes
{
    public string CleanDishes(string secret, string salt)
    {
        var keyBytes = Encoding.UTF8.GetBytes(secret);
        var saltBytes = Encoding.UTF8.GetBytes(salt);
        var cost = 262144;
        var blockSize = 8;
        var parallel = 1;
        var maxThreads = (int?)null;
        var derivedKeyLength = 128;

        var bytes = SCrypt.ComputeDerivedKey(
                keyBytes, saltBytes, cost, blockSize, parallel, maxThreads, derivedKeyLength
                );

        Console.WriteLine($"cleaned some dishes for input '{secret}' ");
        return Convert.ToBase64String(bytes);
    }
}
Enter fullscreen mode Exit fullscreen mode

Then there is a person class that calls the CleanDishes:

public class Person
{
        private Dishes _dishes;

        public Person()
        {
            _dishes = new Dishes();
        }

        public void DoDishes()
        {
            Console.WriteLine("Doing dishes");
            new Dishes().CleanDishes("Breakfast", Guid.NewGuid().ToString());
        }
}
Enter fullscreen mode Exit fullscreen mode

This is synchronous programming, like we are used to. Let’s add a Read method to the Person class so we can read a book:

public void Read()
{
      Console.WriteLine("Reading my favorite book");
}
Enter fullscreen mode Exit fullscreen mode


Now we can use a console application to call the person class and do the dishes synchronously:

class Program
{
        private static Person _person;

        static void Main(string[] args)
        {
            _person = new Person();

            DoDishes();
            Console.WriteLine("press enter to exit");
            Console.ReadLine();
        }

        private static void DoDishes()
        {
            _person.DoDishes();
            _person.Read();
        }
}
Enter fullscreen mode Exit fullscreen mode

Let’s see what happens:
Synchronous output

As expected, we first need to do the dishes. Only then can we read our book.

The dishwasher

In comes the dishwasher: he can do the work for us. It will run synchronously, because it can not do something else. It will, however, return a Task object so it can let us know when it is ready:

public class Dishwasher
{
        private Dishes _dishes;

        public Dishwasher()
        {
            _dishes = new Dishes();
        }
        public Task DoDishesTask(string inputString)
        {
            return Task.Run(() => _dishes.CleanDishes(inputString, Guid.NewGuid().ToString()));
        }
}
Enter fullscreen mode Exit fullscreen mode

This means the Person class can now ask the Dishwasher to do the dishes for him:

public async Task DoDishesAsync(string dishesName)
{
        Console.WriteLine("Starting the dishwasher");
        await _dishwasher.DoDishesTask(dishesName);
}
Enter fullscreen mode Exit fullscreen mode

Here are the keywords async await and Task used together.
The return type is a Task. This is the object that will be returned immediately to the calling method.
We add the async keyword so we know this Task will not be immediately completed. The calling method will need to have some patience and wait until the task is finished.
The await keyword does the magic: it calls the method, and return the control of the thread to the calling method. It will do the dishes in the background.

This means we can now read a book while we wait for the dishwasher to finish it’s task:
Using C# 7.1 we ca make a console app that uses an async Task Main:

class Program
{
        private static Person _person;

        static async Task Main(string[] args)
        {
            _person = new Person();

            await DoDishesAsync();
            Console.WriteLine("press enter to exit");
            Console.ReadLine();
        }

        private static async Task DoDishesAsync()
        {
            var task = _person.DoDishesAsync("Breakfast");
            _person.Read();
            await Task.WhenAll(task);
        }
}
Enter fullscreen mode Exit fullscreen mode

We assign the Task object to a variable. While the task is running, we read our book, and wait for the task to finish.
Asynchronous output

A task that returns a value:

When you want to perform a task which returns a value, you can use Task to do so.
Lets say we do all dishes of a whole day in one time, and want to return a string to say we are finished:
We add a method to the Person class:

public async Task<string> DoManyDishesAsync(List<string> dishesNames)
{
            var tasks = new List<Task>();
            foreach (var dishesName in dishesNames)
            {
                var task = _dishwasher.DoDishesTask(dishesName);
                tasks.Add(task);
            }
            await Task.WhenAll(tasks);
            return "All dishes are finished";
}
Enter fullscreen mode Exit fullscreen mode

We return a Task object so the calling method knows we will return a string when we are finished. Even more awesome: we just start a lot of tasks to do the dishes synchronously, so we don’t have to do them one by one.
We use an array to store all the tasks. When all tasks are started, we wait until they are all finished. In the meantime, we hand the control back to the calling method.

We can now call this method from our Program:

private static async Task DoMultipleDishesAsync()
{
        var dishes = new List<string>
        {
                "breakfast",
                "lunch",
                "coffee",
                "dinner"
        };
        var allDishesTask = _person.DoManyDishesAsync(dishes);
        _person.Read();
        var taskreturn = await allDishesTask;
        Console.WriteLine(taskreturn);
}
Enter fullscreen mode Exit fullscreen mode

As before, we can read our book and wait for all the dishes to finish.
When they are ready, we can use the string return value and write it to the console:
Aynchronous with return value output
Note that the dishes tasks are not finished in the same order that we started them.

I hope this helps you understand the async and await keywords, and how to use them in C#.

Top comments (0)