What's the Difference Between Task.Start/Wait and Async/Await

What's the difference between Task.Start/Wait and Async/Await?

I may be missing something

You are.

what is the difference between doing Task.Wait and await task?

You order your lunch from the waiter at the restaurant. A moment after giving your order, a friend walks in and sits down next to you and starts a conversation. Now you have two choices. You can ignore your friend until the task is complete -- you can wait until your soup arrives and do nothing else while you are waiting. Or you can respond to your friend, and when your friend stops talking, the waiter will bring you your soup.

Task.Wait blocks until the task is complete -- you ignore your friend until the task is complete. await keeps processing messages in the message queue, and when the task is complete, it enqueues a message that says "pick up where you left off after that await". You talk to your friend, and when there is a break in the conversation the soup arrives.

What's the difference between await (async) Task and await Task?

Case B is not async-await.

If you have a thread, and this thread has to start a fairly lengthy process in which it has nothing to do but wait, usually because someone / something else performs the operation, then your thread could decide to do something else instead of waiting for the operation to finish.

Typical operations for this are writing data to a disk, asking for an internet page, doing a database query, etc. On a very low level, your thread can't do anything but wait for the operation to complete.

For instance when writing data to a file, your thread's world ends when it orders the hardware to write the data. Your thread doesn't have to move the write-arm in the hard disk, wait until the correct sector is under the write-arm, send the write signals etc. Your thread can't do anything but wait for completion.

During this time, your thread could start doing something else, and when it has time, it can check if the write operation is finished, and execute the statements after the write operation.

This scenario is described in a kitchen analogy in this interview with Eric Lippert. where a cook doesn't wait for the tea water to boil, but starts slicing the bread. Search somewhere in the middle for async-await

Whenever you call an async function, you can be certain there is an await. In fact, your compiler complains if you write an async function but forget to await somewhere in it.

Whenever your thread enters the async function, it continues working until it sees the await. This indicates that the thread should not perform the statements after the await before the Task that is awaited for is finished and returns.

Normally your thread wouldn't do anything. But in async-await your thread goes up its call stack to perform the functions after the call until it sees the await. It goes up the call stack again, to peform functions until it sees an await, etc.

After everyone is awaiting, the thread can't do anything anymore and is returned to the thread pool. If the process that we were awaiting for (the hard disk write) is finished, a thread is fetched to the thread pool. This thread will continue performing the statements after the await until it sees an await again.

This is described in the article by Stephen Cleary: There is no thread

Quite often you'll see an await immediately after the async-call:

var fetchedItems = await stream.ReadAsync();

In this case the await is immediately after the call. The thread won't do much in this function before ReadAsync is finished.

But sometimes your function doesn't need the result immediately:

var fetchTask = stream.ReadAsync()
// because there is no await, instead of doing nothing the thread can do the following:
DisplayWaitIcon();
CalculateSomething();

// now the thread needs the result. So it starts awaiting:
var fetchedItems = await fetchTask;
// here we know that the ReadAsync is finished,
// and the returned items are available in fetchedItems.
ProcessFetchedItems(fetchedItems);

You see that in my story there is only one thread that is doing all the stuff. If you look closely, it doesn't have to be the same thread that does all the stuff, it might be a different thread.

This can be seen in a debugger, if you investigate the ThreadId. This other thread has the 'context` of the original thread, with the effect that it can act as if it was the original thread. You don't have to worry about multi-threading, mutexes, race conditions etc. For the designer of the program it is as if one thread does all the stuff.

Case B however is not async await. Here you order a thread from a pool of available threads to do something. In the meanwhile your thread is free to do other things until it waits for the task performed by the other thread to completes. Because your function is not async, whenever your thread starts waiting, it doesn't go up its call stack to see if it can do something else, it really waits and does nothing until the task is completed.

This article about async-await written by the ever so helpful Stephen Cleary helped me a lot to understand how to use async-await

What's the difference between starting and awaiting a Task?

A Task returns "hot" (i.e. already started). await asynchronously waits for the Task to complete.

In your example, where you actually do the await will affect whether the tasks are ran one after the other, or all of them at the same time:

await DoOperation0Async(); // start DoOperation0Async, wait for completion, then move on
await DoOperation1Async(); // start DoOperation1Async, wait for completion, then move on
await DoOperation2Async(); // start DoOperation2Async, wait for completion, then move on

As opposed to:

tasks[0] = DoOperation0Async(); // start DoOperation0Async, move on without waiting for completion
tasks[1] = DoOperation1Async(); // start DoOperation1Async, move on without waiting for completion
tasks[2] = DoOperation2Async(); // start DoOperation2Async, move on without waiting for completion

await Task.WhenAll(tasks); // wait for all of them to complete

Update

"doesn't await make an async operation... behave like sync, in this example (and not only)? Because we can't (!) run anything else in parallel with DoOperation0Async() in the first case. By comparison, in the 2nd case DoOperation0Async() and DoOperation1Async() run in parallel (e.g. concurrency,the main benefits of async?)"

This is a big subject and a question worth being asked as it's own thread on SO as it deviates from the original question of the difference between starting and awaiting tasks - therefore I'll keep this answer short, while referring you to other answers where appropriate.

No, awaiting an async operation does not make it behave like sync; what these keywords do is enabling developers to write asynchronous code that resembles a synchronous workflow (see this answer by Eric Lippert for more).

Calling await DoOperation0Async() will not block the thread executing this code flow, whereas a synchronous version of DoOperation0 (or something like DoOperation0Async.Result) will block the thread until the operation is complete.

Think about this in a web context. Let's say a request arrives in a server application. As part of producing a response to that request, you need to do a long-running operation (e.g. query an external API to get some value needed to produce your response). If the execution of this long-running operation was synchronous, the thread executing your request would block as it would have to wait for the long-running operation to complete. On the other hand, if the execution of this long-running operation was asynchronous, the request thread could be freed up so it could do other things (like service other requests) while the long-running operation was still running. Then, when the long-running operation would eventually complete, the request thread (or possibly another thread from the thread pool) could pick up from where it left off (as the long-running operation would be complete and it's result would now be available) and do whatever work was left to produce the response.

The server application example also addresses the second part of your question about the main benefits of async - async/await is all about freeing up threads.

await vs Task.Wait - Deadlock?

Wait and await - while similar conceptually - are actually completely different.

Wait will synchronously block until the task completes. So the current thread is literally blocked waiting for the task to complete. As a general rule, you should use "async all the way down"; that is, don't block on async code. On my blog, I go into the details of how blocking in asynchronous code causes deadlock.

await will asynchronously wait until the task completes. This means the current method is "paused" (its state is captured) and the method returns an incomplete task to its caller. Later, when the await expression completes, the remainder of the method is scheduled as a continuation.

You also mentioned a "cooperative block", by which I assume you mean a task that you're Waiting on may execute on the waiting thread. There are situations where this can happen, but it's an optimization. There are many situations where it can't happen, like if the task is for another scheduler, or if it's already started or if it's a non-code task (such as in your code example: Wait cannot execute the Delay task inline because there's no code for it).

You may find my async / await intro helpful.

What is the difference between Task.Run() and await Task.Run()?

What is the difference between Task.Run() and await Task.Run()?

The first starts a task and then does the work immediately after that task, before the task completes.

The second starts a task and then does different work until the task is completed, at which point it does the work after the task.

Let's make an analogy. Your first program is like doing this:

  • Hire someone to mow the lawn.
  • Tell your spouse the lawn is mowed.
  • Go watch Netflix.

Your second program is:

  • Hire someone to mow the lawn.
  • Watch Netflix while the lawn is being mowed.
  • When the lawn is done being mowed and the movie is over, tell spouse the lawn is mowed.

Clearly those are very different workflows. Both are asynchronous, but only the latter has an asynchronous wait in it. We asynchronously wait to tell the spouse that the lawn is mowed until it actually is mowed.

Understanding the use of Task.Run + Wait() + async + await used in one line

You can break this apart into several parts:

async () => { await SomeClass.Initiate(new Configuration()); }

Is a lambda expression that defines an async method that just awaits another method. This lambda is then passed to Task.Run:

Task.Run(async () => { await SomeClass.Initiate(new Configuration()); })

Task.Run executes its code on a thread pool thread. So, that async lambda will be run on a thread pool thread. Task.Run returns a Task which represents the execution of the async lambda. After calling Task.Run, the code calls Task.Wait:

Task.Run(async () => { await SomeClass.Initiate(new Configuration()); }).Wait();

This will block the main console app until the async lambda is completely finished.

If you want to see how it's broken out further, the following is roughly equivalent:

static async Task AnonymousMethodAsync()
{
await SomeClass.Initiate(new Configuration());
}

static void Main(string[] args)
{
var task = Task.Run(() => AnonymousMethodAsync());
task.Wait();
while (true) ;
}

await Task.Run vs await

Task.Run may post the operation to be processed at a different thread. That's the only difference.

This may be of use - for example, if LongProcess isn't truly asynchronous, it will make the caller return faster. But for a truly asynchronous method, there's no point in using Task.Run, and it may result in unnecessary waste.

Be careful, though, because the behaviour of Task.Run will change based on overload resolution. In your example, the Func<Task> overload will be chosen, which will (correctly) wait for LongProcess to finish. However, if a non-task-returning delegate was used, Task.Run will only wait for execution up to the first await (note that this is how TaskFactory.StartNew will always behave, so don't use that).

Is using async/await better then using task.Start() and why?

new Task will execute the entire method using TaskScheduler.Current, usually this makes use of the ThreadPool.

By using async/await, the method is entered synchronously, and will only use an asynchronous continuation if it is required.

What I mean by this can be demonstrated with the following LINQPad program:

const int delay = 1;

public async Task DoSomethingAsync()
{
Thread.CurrentThread.ManagedThreadId.Dump();
await Task.Delay(delay).ConfigureAwait(false);
Thread.CurrentThread.ManagedThreadId.Dump();
}

void Main()
{
DoSomethingAsync().Wait();
}

Try changing delay to 0, and you will see the the continuation resumes on the same thread, this is because Task.Delay just returns immediately if there is no delay, this avoids the overhead of arranging and executing continuations when they are not required.

By using new Task, you are losing this clever functionality and always using a ThreadPool thread, even when the implementor of an async method may not deem it necessary.



Related Topics



Leave a reply



Submit