Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is a rigorous definition of what should be an "awaitable" task?

I'm trying to understand async-await at a deep level. Whenever I see examples of how it works, the example uses an awaitable 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 awaited 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 between LongRunningTask and this chunk of code. It you haven't finished LongRunningTask by the time you finish this chunk of code, then just work on finishing LongRunningTask, hence why I'm telling you await it."

Can you help straighten me out?

like image 455
user7127000 Avatar asked Dec 09 '16 06:12

user7127000


2 Answers

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.

like image 116
Damien_The_Unbeliever Avatar answered Nov 03 '22 11:11

Damien_The_Unbeliever


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.

like image 1
Mike Nakis Avatar answered Nov 03 '22 13:11

Mike Nakis