-1

After reading a lot about async/await I still have a fundamental question (that I kind of assume the answer but I just want confirmation).

Let's discuss over some code:

async Task methodA() {
    //code A (without awaits or Task.Run)
    Task.Run(()=> //code A' (without awaits or Task.Run))
}

async Task methodB() {
    //code B (without awaits or Task.Run)
    await methodA();
}


void Main() {
    //code C (without awaits or Task.Run)
    methodB(); (no await)
    //code D (without awaits or Task.Run, no pun intended)
}

What I think will happen:

  1. Run code C
  2. Run code B
  3. Run code A
  4. Run code A' and code D (different threads, in parallel)

The doubt comes from what's in the documentation:

The await operator suspends evaluation of the enclosing async method until the asynchronous operation represented by its operand completes. [...] The await operator doesn't block the thread that evaluates the async method.

The key being here what represents an asynchronous operation. Cause considering this other piece of documentation:

The await operator tells the compiler that the async method can't continue past that point until the awaited asynchronous process is complete. In the meantime, control returns to the caller of the async method.

One might think that, since methodA() is async, once methodB reaches the await methodA(), then it might return control to the caller and allow to run code D in parallel to code A and code A'.

The essence is if method A constitutes an asynchronous operation as described in the documentation, or only until it reaches truly async code (code that runs on another thread) then it actually allows control to return to the caller (which in this case is code A').

My understanding is that asynchronous operation is an operation that will run on another thread, is this correct? So things like Task.Run, Task.Delay, Task.Sleep..? And that even though code A is within an async method, it is not an async operation and control does not go immediately back when awaiting methodA. Is this correct?

Just for completion, check this question where in the answer's comments they argue precisely about this, without a very clear answer.

Marc Cayuela
  • 1,504
  • 13
  • 26
  • 3
    _"My understanding is that asynchronous operation is an operation that will run on another thread, is this correct?"_ - No. Or not entirely. It _can_. But there is also for example async I/O, where ["there is no thread"](https://blog.stephencleary.com/2013/11/there-is-no-thread.html). – Fildor Jun 08 '22 at 15:25
  • 1
    Your expected order is correct, although maybe for different reasons than you think. I'm also not sure exactly what is the question - exactly when the execution returns to the caller? At the first *incomplete* `await`. – GSerg Jun 08 '22 at 15:43
  • 2
    If you want to know what happens when you run your code then *run your code and see what it does*. Asking strangers on the internet what will happen when your code runs is a much less reliable way of learning what will happen when you run it. Should the results not be what you expect them to be, and you're unable to figure out why the results are what they are, after looking through the available information on the topic, then *that* could become a question you might ask others about. – Servy Jun 08 '22 at 15:48
  • Oddly enough, the above code completes synchronously as far as `methodA` is concerned, as there are no `await` for the `Task.Run`. Had you awaited it then execution of `methodA` would have been suspended until `Task.Run` completed – Charlieface Jun 08 '22 at 20:58
  • @GSerg what is the "definition" of an *incomplete* ```await```? – Marc Cayuela Jun 09 '22 at 08:25
  • 2
    @MarcCayuela An `await` that is invoked on a `Task` that is not yet [`.IsCompleted`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.iscompleted?view=net-6.0#system-threading-tasks-task-iscompleted). – GSerg Jun 09 '22 at 08:26
  • @MarcCayuela I didn't say that if a code produces the desired result it always will, simply that running your code can tell you a lot about it. You want to know if the code runs in asynchronously or synchronously. Something like that is *very* easy to see by just running it. The behaviors of the two are *very different*. If you did run a bunch of tests then *say that*, and as a question that demonstrates that you did the most basic of research and testing *and how that failed to answer your question* rather than asking a question answered by running your code once and seeing what happens. – Servy Jun 10 '22 at 04:28
  • @MarcCayuela If you did that, and states that you expected it to run asynchronously, but you ran a test, showing an actual example with code that was so quick to run it ran in that order, then that would make for a better question than what you currently have. Of course, you could just write code that makes A not be super fast, since your test relies on that, assumption, so making that assumption true helps the test. But the more likely option is it doesn't actually happen that way and the program runs in an order not possible for synchronous code, so you have no need to ask a question. – Servy Jun 10 '22 at 12:13
  • @Servy Suppose you don't read the documentation. Is there a test code that will prove 100% how the code will run? No. Imagine now you read the documentation and it is not clear. Is there a test code that will prove 100% how the code will run? No. Imagine the documentation is clear. Is there a test code that will prove 100% how the code will run? Yes. Actually if it is very clear you don't need a test. What percentage of certainty do I need for my code? 100%. Hence the question. – Marc Cayuela Jun 10 '22 at 15:09
  • @MarcCayuela So you're 100% certain of what the output of your program will be when you run it because one random stranger on the internet told you what they think it will do? There is no chance at all that they'll be wrong, or that they'll misunderstood your code or your question, or that you misunderstand their response? Again, running your code *gives you lots of valuable information*. It may or may not be sufficient information for you to stop and do nothing more, but even if it isn't *it's still worth doing*, if for no other reason than to construct a better question if it's needed. – Servy Jun 10 '22 at 15:47
  • Your prediction was that the code will run C, B, A, A', D or C, B A, D, A'.. If you run the program and the result is A, A' B C D *you know your prediction is wrong*, if it comes out as B C A A' D, *you know your prediction is wrong*, etc. If you run it and your prediction is correct you don't just stop and assume your prediction is always correct, but if if it's not *you don't need to do anything else, you know your prediction is wrong with 100% certainty. – Servy Jun 10 '22 at 15:53
  • @Servy Yes, you are right. At the end the test is a good start, but cannot be the end proof. – Marc Cayuela Jun 12 '22 at 09:52

1 Answers1

1

[...], control returns to the caller of the async method.

This sentence had confused the hell out of me when I was learning asynchronous programming. What it means is that the Task is created and returned. You can think the asynchronous methods as generators for Task objects. The async method has to do some work in order to create the Task. This work completes when the first await of an incomplete awaitable inside the method is reached. At that point the Task is created, it is handed to the caller of the method, and the caller can do whatever it wants with it.

Usually the caller awaits the task, but it can also do other things before awaiting it, or it might never await it and completely ignore it, although this is not recommended. Generally you want to keep track of your tasks, and not let them run unattended in a fire-and-forget fashion.

When the caller does not await immediately the returned task, and the task was not in a completed state upon creation, you introduce concurrency in your application: More than one things might be happening at overlapping time spans. You can read about the differences between concurrency and parallelism here.

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