0

I write a simple async-await code following. As far as I know, when await in FetchAndShowHeaders method is hit, the method's continuation is the rest of the method that is Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(response.Headers.ToString()));.

The point I don't understand that while await is not yet hit in top-level main method, how is the method completed?

I expect typically that all dots are written, and the method prints the result. But in the middle of writing dots, in which case main() runs, the method completes. How come?

Console.WriteLine("soner1");
Task q = FetchAndShowHeaders("http://www.fultonbank.com");
Console.WriteLine("soner2");
for (int i = 0; i < 500_000_0; i++)
{
    for (int ii = 0; ii < 500; ii++)
    {
        if (i % 500_000 == 0)
            Console.Write(".");
    }
}
Console.WriteLine("\nsoner3");
await q;
Console.WriteLine("soner4");
return;

async Task FetchAndShowHeaders(string url)
{
    using var w = new HttpClient();
    var req = new HttpRequestMessage(HttpMethod.Head, url);
    var response = await w.SendAsync(req, HttpCompletionOption.ResponseHeadersRead);
    Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(
        response.Headers.ToString()));
}

Output: (think about the output word wrap is enabled)

/Users/soner/RiderProjects/yea/yea/bin/Debug/net6.0/yea
soner1
soner2
......................................................................................................
...........................................................................
.........................................................................................................
.................................................."Date:
 Mon, 28 Aug 2023 12:24:07 GMT\nConnection: keep-alive\nSet-Cookie: AWSB9/69VjCnga384ol7q; Expires=Mon, 
04 Sep 2023 12:24:06 GMT; Path=/; SameSite=None; Secure, ASP.NET_SessionId=i2hpoollqqqqud1a
pz2gqac; path=/; HttpOnly; SameSite=Lax, ASP.NET_SessionId=i2hqqqd1apz2gqac; 
path=/; HttpOnly; SameSite=Lax, Fulton.Foundation.ContactIdent
ification.Cookies.ContactIdentificationCookieManagerblablalblalbla"
............................
..........................
.........................................................................................................................
..........................
..........................
.........................................................................
.................................................................
soner3
soner4

Process finished with exit code 0.
Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
  • As far as I understand your question, it's not exactly working as you expect it to work. The `await` is (it's simplification) blocking thread that is running until awaited task completes. Work is being started as soon as program hits `var q = FetchAndShowHeaders("http://www.fultonbank.com");`, but on a separate thread. So at this point you have two threads. One is outputing dots, another is sending request. And end result is what you see. Surprise would be if you saw `sooner3` in a middle of dots ;-) – Lukasz Nowakowski Aug 28 '23 at 12:51
  • Does this answer your question? [How to construct a Task without starting it?](https://stackoverflow.com/questions/16066349/how-to-construct-a-task-without-starting-it) – shingo Aug 28 '23 at 12:52
  • 2
    `await` only awaits an already executing async method, it doesn't start it – Panagiotis Kanavos Aug 28 '23 at 12:52
  • 1
    @shingo that's not a good duplicate. There's almost never any good reason to create cold tasks and call `.Run`. – Panagiotis Kanavos Aug 28 '23 at 12:53
  • @shingo it is not WHAT I asked – Soner from The Ottoman Empire Aug 28 '23 at 12:56
  • @Lukasz Nowakowski yes, bcz there is no `SynchronizationContext` in a console app, a thread is employed from the thread pool. But it is not when it is in the method, but after the `await` in the method. Thank you. By the way, my name is Soner , not sooooner – Soner from The Ottoman Empire Aug 28 '23 at 13:00
  • @PanagiotisKanavos when `await` is hit in the method, a thread is employed and starts running. Is it correct? That's why I see it in the middle. I want to solidify my knowledge. If I'm wrong, could you please fix? – Soner from The Ottoman Empire Aug 28 '23 at 13:02
  • @SonerfromTheOttomanEmpire, that question isn't, but the answers can show you the way to create a task without starting it. – shingo Aug 28 '23 at 13:04
  • @shingo I don't need it. If you got me right, why did you mark it as duplicate? – Soner from The Ottoman Empire Aug 28 '23 at 13:05
  • @SonerfromTheOttomanEmpire, because I thought your question can be answered by that one. Lukasz has mentioned: _"Work is being started as soon as program hits `var q = FetchAndShowHeaders("http://www.fultonbank.com");`"_. To paraphrase with your words: when `FetchAndShowHeaders("http://www.fultonbank.com");` is hit, a thread is employed and starts running. So if you don't want the task to start immediately, you need to create a task. – shingo Aug 28 '23 at 13:22
  • 2
    There is no thread there .... it is asynchronous I/O – Selvin Aug 28 '23 at 13:27
  • The HttpClient operation started at `Task q = FetchAndShowHeaders("http://www.fultonbank.com");` and more specifically `await w.SendAsync`. Not at `await q` - that's when the application started *awaiting* for the async operation to complete. I say *operation*, not thread, because network and IO operations don't use threads. A Task isn't a Thread, it's a promise that something will complete in the future – Panagiotis Kanavos Aug 28 '23 at 13:29
  • @Selvin how is there no thread? It is motto of most of people. What about the lines after `await`s? `Environment.CurrentManagedThreadId` yields different threads. – Soner from The Ottoman Empire Aug 28 '23 at 16:30

1 Answers1

2

The Task q is completed before the await, because tasks don't have to be awaited in order to make progress. When you launch a Task by invoking an asynchronous method, the Task has started. Sometimes the term hot is used: asynchronous methods produce hot tasks.

Tasks that are created by the public Task constructors are referred to as cold tasks, because they begin their life cycle in the non-scheduled Created state and are scheduled only when Start is called on these instances.

All other tasks begin their life cycle in a hot state, which means that the asynchronous operations they represent have already been initiated and their task status is an enumeration value other than TaskStatus.Created.

You don't have to do anything more to start the Task, because it's already started. Certainly you don't have to await it. The reason we await tasks is because we want to suspend the current execution flow until the task is completed. The await affects the current flow, not the task. The task will do its job irrespective of whether you await it. The task don't care if it's awaited or not.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
  • Thank you very much. Some told us _there is no any thread_, but when I write `Environment.CurrentManagedThreadId`, it shows a different thread after `await`ed line. How is there no any thread? I can't get the concept. Can you help me? – Soner from The Ottoman Empire Aug 28 '23 at 16:28
  • 1
    @Soner yes, check out [this](https://blog.stephencleary.com/2013/11/there-is-no-thread.html "There is no thread") comprehensive blog post by Stephen Cleary. This means that there is no thread dedicated to the asynchronous operation from start to finish. When the operation completes, some thread has to do the minuscule work of completing the `Task`, so that the execution flow(s) that `await` the task can continue. This thread is usually a `ThreadPool` thread. A handful of `ThreadPool` threads can complete millions of tasks per second. – Theodor Zoulias Aug 28 '23 at 16:40
  • Thank you. I did read the blog post several times before. However, it is a bit hard to understand. Would you mind advising me to read some other resources to comprehend the concept well? – Soner from The Ottoman Empire Aug 28 '23 at 16:55
  • 1
    @Soner there is a short summary of this blog post [here](https://stackoverflow.com/questions/37419572/if-async-await-doesnt-create-any-additional-threads-then-how-does-it-make-appl/37419836#37419836). My personal understanding, which btw is **not** based on detailed knowledge of how the hardware works, is that the asynchronous work is done by other hardware than the CPU, for example by the network card. The only hardware where the concept of "thread" applies is the CPU. So when there is nothing for the CPU to do, there is nothing for a thread to do either. – Theodor Zoulias Aug 28 '23 at 17:22