I'm trying to understand async
-await
at a deep level. Whenever I see examples of how it works, the example uses an await
able method called LongRunningTask
or similar and in it is a Task.Delay
or a new WebClient().Download("http://google.com")
or something similar. I'm trying to figure out what would be a rigorous definition of what should be awaited on. It can't simply be "a long-running task", because a long-running task could be something like finding the maximum subarray of an array of size 1,000,000,000, which wouldn't confer any performance benefit to being await
ed on if it's true that async
-await
doesn't create any new threads.
From what I understand, if you have something like
var task = LongRunningTask();
DoSomething();
int x = DoSomethingElse();
int y = await task;
int z = x + y;
then the chunk
DoSomething();
int x = DoSomethingElse();
is what you're hinting to the compiler that
"Hey, compiler, if you can verify that this chunk doesn't have any dependencies on
LongRunningTask
and vice-versa, then you can start dividing your work betweenLongRunningTask
and this chunk of code. It you haven't finishedLongRunningTask
by the time you finish this chunk of code, then just work on finishingLongRunningTask
, hence why I'm telling youawait
it."
Can you help straighten me out?
await
is less magic than you think. In your example, the decision on whether to use other threads, or I/O completion routines, etc, isn't in any of the code you've shown.
The implementation of LongRunningTask
, by being a method that returns an already started Task
, has made any complex decisions about how and when that Task
gets completed.
All that await
does is to wait (as its name implies) for something that has already been started to complete.
Now, if LongRunningTask
is implemented by creating new Tasks, and those tasks are using the default scheduler that uses that thread pool, and if your current method is already running on a thread pool thread and some of the tasks created by LongRunningTask
are waiting for a thread pool thread to become available, then it is possible that the very same thread that had been running your code becomes available when the await
is encountered and will be used to start running one of the waiting tasks. But this is mostly something you don't need to think about.
For the purposes of your question, a long-running task is any task that involves Input/Output (I/O).
This is the par excellence class of tasks that can be awaited, because what usually happens with I/O is that the computer issues instructions to commence the I/O, and then simply waits (usually in a low-CPU state) for the I/O to complete.
So, new WebClient().Download("http://google.com")
is a good example of a task that involves I/O, because in order to perform this task, the machine will need to prepare a request to google.com, start sending it over a socket, wait for the request to be completely sent, then wait for a response to start arriving, then wait until the response has completely arrived.
However, this definition is not strict; even finding the maximum subarray of an array of size 1,000,000,000 could be a long-running task, if it was implemented by spawning a new thread, because as far as the calling thread is concerned, we would have a very similar scenario: we set-up the operation, (spawn the new thread, passing it the array to be searched,) and then wait for the results, doing nothing.
Edit
In light of the above, the maximum-subarray-of-huge-array example could be thought of as a red herring, because for the purpose of async-await, the question of whether it is or it isn't a long-running task depends on whether it is launched in a separate thread or not.
So, a much better definition of a long-running task could be:
Any task that takes such an amount of time to complete that it is undesirable to block waiting for in the currently executing thread.
So, for example, if you are writing an interactive (GUI) application, and you want to guarantee a 200 millisecond response time to your human user, then any task that is bound to take longer than that is a long-running task: you start it, await for it, and for as long as it is running your GUI is still responsive. On the other hand, if you are writing a batch processing job which is meant to run overnight to crunch some big data, then virtually no task is worth being considered as a long-running task.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With