0

Below code is from https://blog.stephencleary.com/2012/02/async-and-await.html I just added some descriptive methods

class Program
{
    async static Task Main(string[] args)
    {
        Console.WriteLine(Thread.CurrentThread.ManagedThreadId); //thread id is 1 here
        await DoSomethingAsync();
        Console.WriteLine("do other workzzz");
        Console.WriteLine(Thread.CurrentThread.ManagedThreadId);  //thread id is 4 here
        Console.ReadLine();
    }

    public static async Task DoSomethingAsync()
    {
        await Task.Delay(2000);    
    }
}

and the author says:

I like to think of “await” as an “asynchronous wait”. That is to say, the async method pauses until the awaitable is complete (so it waits), but the actual thread is not blocked (so it’s asynchronous).

so my questions is: why the actual thread(thread id 1 in my case) is not blocked? From my perspective, a thread is blocked because further statements will not be executed until current method has finished. We can see that Console.WriteLine("do other workzzz"); won't be executed until DoSomethingAsync() finishes, isn't it kind of blocking?

Another important thing to notice is, after DoSomethingAsync(); finishes, thread id changes from 1 to 4, there is no "actual thread" anymore. Why thread 1 disappear? shouldn't it be the thread 4 to disappear?

  • Possible duplicate of [How to call asynchronous method from synchronous method in C#?](https://stackoverflow.com/questions/9343594/how-to-call-asynchronous-method-from-synchronous-method-in-c) – Allan S. Hansen Jul 09 '19 at 05:31
  • 1
    @AllanS.Hansen That's not really what this post is about – FCin Jul 09 '19 at 05:32
  • Use `await Task.WhenAll(taskA, taskB, …);` to await multiple tasks at once. – ckuri Jul 09 '19 at 05:33
  • @ckuri I just want to know if part B can be called synchronously, aka async/await also run synchronously. –  Jul 09 '19 at 05:34
  • It is still an asynchronous method. If you look at the method signature it will say `... async Task ...`, so you cannot call it synchronous. Even though it executes in 5 seconds it can still do background work while it is waiting for each task to finish. There is a big difference between `await Task.Delay(1000);` and `Thread.Sleep(1000);` – FCin Jul 09 '19 at 05:37
  • @FCin so what's the big difference between await Task.Delay(1000); and Thread.Sleep(1000)? –  Jul 09 '19 at 05:44
  • Thread.Sleep will block the current thread and await Task.Delay will continue on the current synchronization context – Sir Rufo Jul 09 '19 at 05:48
  • @ckuri but if you run `await Task.Delay(1000);Console.WriteLine("do more work");await Task.Delay(1000);`, is the same effect as `await Thread.Sleep(1000);Console.WriteLine("do more work")Thread.Sleep(1000);` –  Jul 09 '19 at 05:53
  • @slowjams If you want to **feel** the difference then build a winforms app with 2 buttons. first button in on click event call Thread.Sleep(10000) and second button in async on click event call await Task.Sleep(10000). Press one of the buttons and try to move the window. Thats is the difference – Sir Rufo Jul 09 '19 at 05:54
  • It still asynchronous. However, such an await doesn't mean much to a console because there is no synchronization context or other task/thread running in the background. However, in a GUI (WinForms or WPF) you can see it's asynchronous because while you await, the application still processes user inputs which wouldn't happen with Thread.Sleep as this puts the (GUI) thread which handles user inputs to sleep. – ckuri Jul 09 '19 at 05:54
  • @SirRufo I have tried that with WPF. so could you use concise sentence to explain what synchronization context is? I tried to google it, and it is too hard for me to understand? is it sth to do with main thread? I notice the main thread will disappear after await finishes, a new thread will be executing the rest of work. –  Jul 09 '19 at 06:02
  • 2
    @slowjams read the [blog of Stephen Cleary](https://blog.stephencleary.com/) and all the articles about async/await, Tasks etc. - there are a lot and every one of it worth reading. That should answer all of your async/await questions – Sir Rufo Jul 09 '19 at 06:10
  • The flow is blocked, not the thread. As explained by ckuri the fact the thread isn't blocked has to do with the synchronizationcontext of a console app. A synchronizationcontext is responsible for scheduling and dispatching queued operations in a threaded context. –  Jul 10 '19 at 06:32
  • When a thread is awaiting completion of a task it can go on to do some other things. Such as handling mouse/keyboard events or http requests. – FCin Jul 10 '19 at 06:48

4 Answers4

0

(Original question was along the lines of: "What do you call an async method when you await it rather than letting it run in parallel?")

The word I would use is "sequentially". They are running one at a time, in sequence. But that type of task (async) is still asynchronous, due to the way it is scheduled and executed. It's more than just the caller that decides whether it is asynchronous or not. (Additionally, the caller is async, and its caller will also be async, so you really can't call an asynchronous task synchronously. (If you try, using functions like Wait(), you risk deadlocks. Because under the hood, it's not the same as a synchronous function.)

piojo
  • 6,351
  • 1
  • 26
  • 36
  • so what makes asynchronous function asynchronous in term of schedule and execution? –  Jul 09 '19 at 05:48
  • @slowjams In the .NET side, there is a task scheduler that decides what to run. It will allocate tasks to threads and start/pause/stop tasks at points they can be paused. On the OS side, .NET tasks can be paused until the OS has something new for them (such as if a read or write has completed). I'm fuzzy on the details, but the .NET side of things is described (in dense detail) here: https://learn.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/consuming-the-task-based-asynchronous-pattern – piojo Jul 09 '19 at 05:54
  • 1
    @slowjams: `what makes asynchronous function asynchronous` It returns to its caller. See my [async intro](https://blog.stephencleary.com/2012/02/async-and-await.html). – Stephen Cleary Jul 10 '19 at 03:46
  • @StephenCleary I read your blog and edit my question, could you have a look again, please? –  Jul 10 '19 at 06:30
  • 1
    @slowjams: You pretty much completely changed the question, and now most of the answers don't make sense. Stack Overflow is a Q&A site, so it's best to ask a new question if you have major changes. – Stephen Cleary Jul 10 '19 at 12:34
0

I think the lack of variable assignments in part B is confusing you.

Part B can be rewritten as this:

var a = Task.Delay(1000);
await a;

var b = Task.Delay(1000);
await b;

var c = Task.Delay(1000);
await c;

etc...

If you invoke all the tasks at the beginning and await them all at the end (as in part A), then you can think of it as those tasks running in parallel.

await means to wait for the task (in this case, Task.Delay(1000)) to complete before moving on. Without the await, you can think of the task as being run in the background (but the main program can exit before it reaches completion)

To wait until all tasks are finished before the program exits, you can do something like this: await Task.WhenAll(taskA, taskB, …);

Ambrose Leung
  • 3,704
  • 2
  • 25
  • 36
0

Async operations are handled differently than other methods.

Simplified explanation: your code..

async static Task Main(string[] args) 
{
    Console.WriteLine(Thread.CurrentThread.ManagedThreadId); //thread id is 1 here
    await DoSomethingAsync();
    Console.WriteLine("do other workzzz");
    Console.WriteLine(Thread.CurrentThread.ManagedThreadId);  //thread id is 4 here
    Console.ReadLine(); 
}

Gets executed as follows:

The calling thread executes..

Console.WriteLine(Thread.CurrentThread.ManagedThreadId);

The code waited upon by the await statement gets dispatched to the task scheduler, which uses a synchronization context to queue, schedule and execute it. The implementation of the synchronization context is responsible for deciding which thread this code executes in.

 await DoSomethingAsync();

The code after the await gets posted back to synchronization contex captured at the start. Again, depending on the implemenation, some other thread might execute it.

Console.WriteLine("do other workzzz");
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
Console.ReadLine(); 

Eventhough the code reads synchronously, the async/await pattern will break up the flow.

  • 2
    "The await statement gets dispatched to the task scheduler, which uses a synchronization context to queue, schedule and execute it." Not really. The `DoSomethingAsync()` method is called, which returns a task. Then the `await` operator comes into effect: if that task is already completed, execution continues normally. Otherwise, a continuation is scheduled for when the task *has* completed. The way you've written it makes it sound like `DoSomethingAsync()` is "dispatched to the task scheduler". It's only the continuation. – Jon Skeet Jul 10 '19 at 06:51
  • @JonSkeet Updated. Am I correct about the continuation being post back to the captured synchronization context? –  Jul 10 '19 at 06:55
  • 1
    By default, with `Task`, yes - but it's really up to what the awaitable does. So `ConfigureAwait(false)` returns an awaitable that *doesn't* do that. – Jon Skeet Jul 10 '19 at 07:13
0

why the actual thread(thread id 1 in my case) is not blocked?

When await is used on an awaitable that is not complete, then that await returns from the async method. The calling thread continues executing its next piece of code.

We can see that Console.WriteLine("do other workzzz"); won't be executed until DoSomethingAsync() finishes, isn't it kind of blocking?

It doesn't block a thread. The method is "paused" at the point of the await, but there's no thread holding it in place.

For example, examine your call stack before the await and again after the await. You'll see that the call stack is not preserved, because the async method returned and the original thread continued running. After the await, that call stack is not re-entered.

Another important thing to notice is, after DoSomethingAsync(); finishes, thread id changes from 1 to 4, there is no "actual thread" anymore. Why thread 1 disappear? shouldn't it be the thread 4 to disappear?

Both threads 1 and 4 are actual threads. Thread 1 is the main thread, the one that starts the Console application. Thread 4 is a thread pool thread.

When DoSomethingAsync finishes and the await is ready to resume its async method, it has to resume it somewhere. By default an await will capture a "context"; for a console application, this is the thread pool context. So when the async method resumes, it resumes on a thread pool thread.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810