Async / Await with threads (C # 7.2)

6

I have this code and as you can see I created two examples, Parallel and NotParallel.

I was hoping that both of them would return me 3000ms, as both should run async (2000 and 3000) and the total time would be 3000, but the NonParallel, is taking 5000ms, where is the sum of the two, looking like one expects the other to finish .. even with async.

static class Program
{
    static async Task<string> GetTaskAsync(int timeout)
    {
        Console.WriteLine("Task Thread: " + Thread.CurrentThread.ManagedThreadId);
        await Task.Delay(timeout);
        return timeout.ToString();
    }

    static async Task Main()
    {
        Console.WriteLine("Main Thread: " + Thread.CurrentThread.ManagedThreadId);

        Console.WriteLine("Should be greater than 5000");
        await Watch(NotParallel);
        Console.WriteLine("Should be less than 5000");
        await Watch(Parallel);
    }

    public static async Task Parallel()
    {
        var res1 = GetTaskAsync(2000);
        var res2 = GetTaskAsync(3000);

        Console.WriteLine("result: " + await res1 + await res2);
    }

    public static async Task NotParallel()
    {
        var res1 = await GetTaskAsync(2000);
        var res2 = await GetTaskAsync(3000);

        Console.WriteLine("result: " + res1 + res2);
    }

    private static async Task Watch(Func<Task> func) {
        var sw = new Stopwatch();
        sw.Start();

        await func?.Invoke();

        sw.Stop();
        Console.WriteLine("Elapsed: " + sw.ElapsedMilliseconds);
        Console.WriteLine("---------------");
    }
}

Result Example:

    
asked by anonymous 20.04.2018 / 14:24

2 answers

8

A common mistake is to think that asynchronism is synonymous with parallelism. The use of async is not always well understood either.

Using async , by itself, does not make the method (the code that executes) asynchronous. async only allows the word await to be used.
What allows certain code to be executed asynchronously is the Task.

The combined use of async / await allows Write / Mix Synchronous and Asynchronous Code Shape sequential. Each line of code runs sequentially, when a line with await is encountered the execution of the method is suspended and execution is returned to the calling code. When the asynchronous operation terminates, execution resumes on the next line and proceeds sequentially.

This misunderstanding makes it possible to use async in methods where it is not necessary:

private async Task<Resultado> MetodoAsync()
{
    ....
    ....
    return await opAsync();
}

Only need await if, in that method, you need to do something with the result and / or after the asynchronous operation ends. If this is not the case, do not use async , just return to Task.

private Task<Resultado> Metodo()
{
    ....
    ....
    return OpAsync();
}

When you're ready to deal with the result, then yes, use async/await

That said, let's look at each of the methods:

  • NotParallel ()

    Each of the GetTaskAsync() calls is made with await . When executing the first call ( GetTaskAsync(2000) ) the execution is released (the method returns immediately), waiting for it to finish, and then proceeding with the second call ( GetTaskAsync(3000) ). That is, GetTaskAsync(3000) is only called after GetTaskAsync(2000) is finished.

  • Parallel ()

    Since wait is not used in the GetTaskAsync() call, calls are executed immediately one after the other with tasks running in parallel ("concurrent law").

I've simplified the code to try to show that "parallelism" has, in this case, only to do with the moments (instants) in which each call to GetTaskAsync() is made. In this situation ( console application ), only the GetTaskAsync() method needs to be declared async . It is the only one that needs to release the execution to allow it to be called again in the Parallel() method before terminating the previous call.

static class Program
{
    static async Task<string> GetTaskAsync(int timeout)
    {
        Console.WriteLine("Task Thread: " + Thread.CurrentThread.ManagedThreadId);
        await Task.Delay(timeout);
        return timeout.ToString();
    }

    private static void Main()
    {
        Console.WriteLine("Main Thread: " + Thread.CurrentThread.ManagedThreadId);

        Console.WriteLine("Should be greater than 5000");
        Watch(NotParallel);
        Console.WriteLine("Should be less than 5000");
        Watch(Parallel);
        Console.ReadKey();
    }

    public static void Parallel()
    {
        var res1 = GetTaskAsync(2000);
        var res2 = GetTaskAsync(3000);

        Console.WriteLine("result: " + res1.Result + " " + res2.Result);
    }

    public static void NotParallel()
    {
        var res1 = GetTaskAsync(2000).Result;
        var res2 = GetTaskAsync(3000).Result;

        Console.WriteLine("result: " + res1 + " " + res2);
    }

    private static void Watch(Action func)
    {
        var sw = new Stopwatch();
        sw.Start();

        func();

        sw.Stop();
        Console.WriteLine("Elapsed: " + sw.ElapsedMilliseconds);
        Console.WriteLine("---------------");
    }
}
    
20.04.2018 / 18:00
6

The sum in NotParalel() is just because you're waiting for one execution to finish and then run the next one and finally return, on account of await .

public static async Task NotParallel()
{
    //A atribuição de res1 deve esperar o resultado de GetTaskAsync()
    var res1 = await GetTaskAsync(2000);

    //Somente depois de res1 receber sua atribuição a de res2 deve esperar
    // o novo GetTaskAsync(3000)
    var res2 = await GetTaskAsync(3000);

    //Logo o resultado será no mínimo 5000ms porque ele parou as duas vezes
    Console.WriteLine("result: " + res1 + res2);
}

Now in the parallel, see the difference:

public static async Task Parallel()
{
    //Dispara a atribuição de assíncrona de res1 e segue a execução
    var res1 = GetTaskAsync(2000);

    //Dispara a atribuição assíncrona de res2 e segue a execução
    var res2 = GetTaskAsync(3000);

    //Agora essa linha espera até que res1 e res2 tenham recebido
    //suas atribuições e como a maior espera é de res2, res1 já terá
    //recebido a sua antes e o tempo total de espera é de apenas 3000ms 
    Console.WriteLine("result: " + await res1 + await res2);
}

This is precisely why one method is called a parallel and the other is not. This is the difference in execution time between the two and there you will begin to give due importance to parallelism and asynchronous programming to get the best performance in time in your applications.

For a more in-depth look at the subject, you can refer to Microsoft's own recommendations for this type and implementation in C #.

Async Programming with Async and Await (C # and Visual Basic)

Task-Based Asynchronous Programming

    
20.04.2018 / 17:59