2

Related to this question: Does await completely blocks the thread?

[...] it will first check to see if the called method completed, and if not will register the continuation and return from that method call. Later, once that method completes, it will re-enter the state-machine in order to complete the method

And to this question also: When is the best place to use Task.Result instead of awaiting Task

await simply means "this workflow cannot progress further until this task is completed, so if it is not complete, find more work to do and come back later"

And finally to this post: https://blog.stephencleary.com/2012/02/async-and-await.html

If “await” sees that the awaitable has not completed, then it acts asynchronously. It tells the awaitable to run the remainder of the method when it completes, and then returns from the async method. Later on, when the awaitable completes, it will execute the remainder of the async method. If you’re awaiting a built-in awaitable (such as a task), then the remainder of the async method will execute on a “context” that was captured before the “await” returned.

So from these posts I get that the await operator does indeed not block, but when I've tried to test it i just cannot get this principle to work the way it states to work. Obviously I'm missing something:

    //This will take 10 seconds
    [HttpGet("test1")]
    public async Task<TimeSpan> test()
    {
        var t1 = DateTime.Now;

        var wait1 = DoAsyncEcho("The first!", 10000);
        var wait2 = DoAsyncEcho("The second!", 10000);


        _logger.LogInformation(await wait1);
        _logger.LogInformation(await wait2);
        _logger.LogInformation("DONE!");

        var t2 = DateTime.Now;
        return t2 - t1;
    }

    //This will take 10 seconds too
    [HttpGet("test2")]
    public async Task<TimeSpan> test2()
    {
        var t1 = DateTime.Now;

        var wait1 = DoAsyncEcho("The first!", 10000);
        var wait2 = DoAsyncEcho("The second!", 10000);

        Thread.Sleep(10000);

        _logger.LogInformation(await wait1);
        _logger.LogInformation(await wait2);
        _logger.LogInformation("DONE!");

        var t2 = DateTime.Now;
        return t2 - t1;
    }


    //This will take 20
    [HttpGet("test3")]
    public async Task<TimeSpan> test3()
    {
        var t1 = DateTime.Now;

        var wait1 = await DoAsyncEcho("The first!", 10000);
        var wait2 = await DoAsyncEcho("The second!", 10000);

        _logger.LogInformation(wait1);
        _logger.LogInformation(wait2);
        _logger.LogInformation("DONE!");

        var t2 = DateTime.Now;
        return t2 - t1;
    }



    //This will take 30
    [HttpGet("test4")]
    public async Task<TimeSpan> test4()
    {
        var t1 = DateTime.Now;

        var wait1 = await DoAsyncEcho("The first!", 10000);
        var wait2 = await DoAsyncEcho("The second!", 10000);

        Thread.Sleep(10000);

        _logger.LogInformation(wait1);
        _logger.LogInformation(wait2);
        _logger.LogInformation("DONE!");

        var t2 = DateTime.Now;
        return t2 - t1;
    }

    private Task<string> DoAsyncEcho(string v, int t)
    {
        return Task<string>.Factory.StartNew(() =>
            {
                Thread.Sleep(t);
                return v;
            }
        );
    }

As I see from the methods test3 and test4, await does indeed wait, it does not enter into a state-machine and does a callback later on because it waits the full 10 seconds of the first DoAsyncEcho and then another 10s on the second call. On the methods test1 and test2 execution time lasts for 10s as code does not waits for the return of the DoAsyncEcho but only it awaits for the result later. Particulary test2 method sleeps the 3 calls of 10 seconds in parallel so after all it's just a 10s run.

What do I'm missing here?

JJCV
  • 326
  • 2
  • 19
  • 1
    `What do I'm missing here?` What results did('nt) you expect and why? Your timings seem reasonable to me. – tkausl Oct 25 '19 at 07:55
  • Because of the questions posted and the article I was specting for await to not wait, whether is written directly on the async call or later on when the value is used. I expected it to enter in the so called state-machine which remembers the state of the calls and only waits for them when is absolutely necessary to wait to the result value. tl;dr; for all the methods to run for the same amount of time – JJCV Oct 25 '19 at 07:58
  • `await X();` will wait for the task to complete before continuing with the next statement. This is what you are seeing. – Matthew Watson Oct 25 '19 at 07:59
  • so `await` does wait and block? – JJCV Oct 25 '19 at 08:00
  • 1
    It waits _asynchronously_, it doesn't block the thread. – tkausl Oct 25 '19 at 08:02
  • 1
    In first test you start tasks almost at the same time, so they will finish also almost in the same time ( around 10 seconds). In the test2 it the same, but you sleep for 10 seconds (before tasks will finish). After this sleep time tasks will be finished and that's why time is also around 10 second. In test3 you wait until first task will finish, then wait until 2d task will finish that's why it is 20 seconds. In test4 the same plus 10 seconds for Sleep – Sasha Oct 25 '19 at 08:03
  • The key thing here is that using `await` from a UI thread is special - it doesn't block the UI because it will use "BeginInvoke()` (or equivalent) to call back to continue with the code after the `await`, and just returns from the method so the UI can continue. – Matthew Watson Oct 25 '19 at 08:03
  • I don't fully understand what a *asynchronous wait* is. As I see in the example, at the end wait *asynchronously* is the same as a classic wait that *blocks* because the time keeps adding up. It does not parallelize the threads, it *synchronously* waits for them to complete to retrieve the value, asign it to the var and then continue with the next sentence – JJCV Oct 25 '19 at 08:14
  • It does not block thread, where from it was called. Also it all depends what do you expect. If you need results at that point where you call async method, you use await. If you need to get them later, you do not need to wait, you will get back task and you can use it to get results later – Sasha Oct 25 '19 at 08:18
  • The awaitable method returns a "hot task", i.e. a task that has already been started. If you `await` that task, then the statements following the await will not be executed until the task completes. However, this does not block a UI thread because the method calling `await X()` returns at that point, and the code to continue after the await will be called via `Control.Invoke()` or equivalent. – Matthew Watson Oct 25 '19 at 08:22
  • The tests procedure the expected result as @Sasha explained in the previous comment. When a task is declared and hold by a variable, it is executed immediately and return the caller a continuation token. And the flow moves on. However, if it is declared as await, the flow is stopped until the task completes. AKA, it is executed look-like synced. 2 await next each other, total time is added up. – Thai Anh Duc Oct 25 '19 at 08:23

2 Answers2

4

I think the best way to demonstrate this is through a simple Windows Forms app.

Create a default Windows Forms app and drop 3 buttons onto it (called button1, button2 and button3.

Then add the following code:

async void button1_Click(object sender, EventArgs e)
{
    this.Text = "[button1_Click] About to await slowMethodAsync()";
    int result = await slowMethodAsync();
    this.Text = "[button1_Click] slowMethodAsync() returned " + result;
}

void button2_Click(object sender, EventArgs e)
{
    this.Text = "[button2_Click] About to start task to call slowMethod()";
    int result = 0;

    Task.Run(() =>
    {
        result = slowMethod();
    }).ContinueWith(_ =>
    {
        this.Invoke(new Action(() =>
        {
            this.Text = "[button2_Click] slowMethod() returned " + result;
        }));
    });
}

void button3_Click(object sender, EventArgs e)
{
    this.Text = "[button3_Click] About to call slowMethod()";
    int result = slowMethod();
    this.Text = "[button3_Click] slowMethod() returned " + result;
}

static async Task<int> slowMethodAsync()
{
    await Task.Delay(5000);
    return 42;
}

static int slowMethod()
{
    Thread.Sleep(5000);
    return 42;
}

If you try this code out, you will notice the following:

Pressing button1 will immediately change the title to [button1_Click] About to await Task.Delay(5000), and you can resize the dialog while waiting for 5 seconds, after which the title will change to [button1_Click] Awaited Task.Delay(5000).

The code for handling button2 is very roughly equivalent to the state machine that is generated from the await code for button1. If you press button2, you will see similar effects to pressing button1.

(The actual code for await is in reality quite different, but the underlying mechanism of using a continuation - i.e., ContinueWith() and Invoke(), to continue executing the code after the await on the UI thread illustrates its approach.)

The code for button3 completely blocks during the Thread.Sleep(), and if you press button3 the UI locks up completely for 5 seconds,.


To illustrate what happens with a non-UI example, consider the following console application:

using System;
using System.Threading;
using System.Threading.Tasks;

namespace Demo
{
    static class Program
    {
        static async Task Main()
        {
            Console.WriteLine("Main thread ID = " + Thread.CurrentThread.ManagedThreadId);

            int result = slowMethod();

            Console.WriteLine("result = " + result);
            Console.WriteLine("After calling slowMethod(), thread ID = " + Thread.CurrentThread.ManagedThreadId);

            result = await slowMethodAsync();
            Console.WriteLine("result = " + result);
            Console.WriteLine("After calling slowMethodAsync(), thread ID = " + Thread.CurrentThread.ManagedThreadId);
        }

        static async Task<int> slowMethodAsync()
        {
            await Task.Delay(5000);
            return 42;
        }

        static int slowMethod()
        {
            Thread.Sleep(5000);
            return 42;
        }
    }
}

If you run that, you will see output similar to the following:

Main thread ID = 1
result = 42
After calling slowMethod(), thread ID = 1
result = 42
After calling slowMethodAsync(), thread ID = 4

Note how the code has resumed on a different thread after the await.

The key thing to realise is that as far as calling code is concerned, y = await X(); does not return until it has a value to return, and the code that runs afterwards may be running on a different thread.

The effect of this in terms of blocking THREADS is that the calling thread is freed up to go off and execute some other code, and another thread is only required when the async method returns.

In many cases, this means that no additional thread is required (for the continuation), and in all cases it means that the original calling thread is not blocked and can be freed up to the thread pool for use for another task.

This is the "non blocking" part of all this.

For a good, detailed explanation of why sometimes no additional thread is needed, read Stephen Cleary's excellent "There is no thread".

Matthew Watson
  • 104,400
  • 10
  • 158
  • 276
  • Perfectly explained. I get it with your example but everything is void so the code doesn't have to do nothing with the returned values because there are no ones. In my example await does have to do something with the values returned and in that scenario it does block so I fail to see the state-machine as the calling code blocks adding up the time. – JJCV Oct 25 '19 at 08:36
  • @JJCV I've modified the code so that it `awaits` something that returns a value, but it's really not much different. – Matthew Watson Oct 25 '19 at 08:45
  • Thanks for the edits. So the "non-blocking" part actually refers not to the code not being able to execute more lines but to the thread to be freed to execute any other bunch of lines? This in a netcore api project (which is my scenario), will be equivalent to the thread that executes the await that is released to execute another incomming call. Is this correct? – JJCV Oct 25 '19 at 09:13
  • @JJCV Yes, that's the gist of it! In practical terms, it's more for ASP.Net and UI-based apps which don't like the calling thread to be blocked. – Matthew Watson Oct 25 '19 at 09:20
  • Beautifully explained! Thanks! – JJCV Oct 25 '19 at 09:22
0

It seems that you are confusing two different interpretations of wait and block. The objective of asynchronous code is to block your code, while the thread remains unblocked. If you don't want to block your code, then the solution is easy: don't use await. But if you don't block your code, then you can't use the result of the asynchronous operation, because the asynchronous operation is running concurrently with your code.

What has not happened yet belongs to the future, and the future is unknown. Not only do you not know the result, you don't even know if the operation succeeded or failed. In most cases this is problematic. You need the result of the operation before continuing with processing this result. So you must block your code. And this is why await was invented, to block your code without having to block a thread too.

You need the thread to remain unblocked, so that it continues running the UI message pump that keeps your application responsive. Just because your code is blocked, your _application) need not to be blocked also. For ASP.NET applications, you need the thread to remain unblocked so that it can serve other incoming web requests. The fewer threads you block, the more requests you can serve. In this case async/await becomes a booster of scalability.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104