await task.run vs await c#

2019-03-18 22:37发布

I've searched the web and seen alot of questions regarding task.run vs await async but there is this specific usage scenario where I don't not really understand the difference. Scenario is quite simple i believe.

await Task.Run(() => LongProcess());

vs

await LongProcess());

where LongProcess is a async method with a few asynchronous calls in it like calling db with await ExecuteReaderAsync() for instance.

Question:

Is there any difference between the two in this scenario? Any help or input appreciated, thanks!

2条回答
闹够了就滚
2楼-- · 2019-03-18 22:52

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).

查看更多
祖国的老花朵
3楼-- · 2019-03-18 22:58

Quite often people think that async-await is done by several threads. In fact it is all done by one thread.

See the addition below about this one thread statement

The thing that helped me a lot to understand async-await is this interview with Eric Lippert about async-await. Somewhere in the middle he compares async await with a cook who has to wait for some water to boil. Instead of doing nothing, he looks around to see if there is still something else to do like slicing the onions. If that is finished, and the water still doesn't boil he checks if there is something else to do, and so forth until he has nothing to do but wait. In that case he returns to the first thing he waited for.

If your procedure calls an awaitable function, we are certain that somewhere in this awaitable function there is a call to an awaitable function, otherwise the function wouldn't be awaitable. In fact, your compiler will warn you if you forget to await somewhere in your awaitable function.

If your awaitable function calls the other awaitable function, then the thread enters this other function and starts doing the things in this function and goes deeper into other functions until he meets an await.

Instead of waiting for the results, the thread goes up in his call stack to see if there are other pieces of code he can process until he sees an await. Go up again in the call stack, process until await, etc. Once everyone is awaiting the thread looks for the bottom await and continues once that is finished.

This has the advantage, that if the caller of your awaitable function does not need the result of your function, but can do other things before the result is needed, these other things can be done by the thread instead of waiting inside your function.

A call without waiting immediately for the result would look like this:

private async Task MyFunction()
{
    Task<ReturnType>taskA = SomeFunctionAsync(...)
    // I don't need the result yet, I can do something else
    DoSomethingElse();

    // now I need the result of SomeFunctionAsync, await for it:
    ReturnType result = await TaskA;
    // now you can use object result
}

Note that in this scenario everything is done by one thread. As long as your thread has something to do he will be busy.

Addition. It is not true that only one thread is involved. Any thread who has nothing to do might continue processing your code after an await. If you check the thread id, you can see that this id can be changed after the await. The continuing thread has the same context as the original thread, so you can act as if it was the original thread. No need to check for InvokeRequired, no need to use mutexes or critical sections. For your code this is as if there is one thread involved.

The link to the article in the end of this answer explains a bit more about thread context

You'll see awaitable functions mainly where some other process has to do things, while your thread just has to wait idly until the other thing is finished. Examples are sending data over the internet, saving a file, communicating with a database etc.

However, sometimes some heavy calculations has to be done, and you want your thread to be free to do something else, like respond to user input. In that cask you can start an awaitable action as if you called an async function.

Task<ResultType> LetSomeoneDoHeavyCalculations(...)
{
    DoSomePreparations()
    // start a different thread that does the heavy calculations:
    var myTask = Task.Run( () => DoHeavyCalculations(...))
    // now you are free to do other things
    DoSomethingElse();
    // once you need the result of the HeavyCalculations await for it
    var myResult = await myTask;
    // use myResult
    ...
}

Now a different thread is doing the heavy calculations while your thread is free to do other things. Once it starts awaiting your caller can do things until he starts awaiting. Effectively your thread will be fairly free to react on user input. However, this will only be the case if everyone is awaiting. While your thread is busy doing things your thread can't react on user input. Therefore always make sure that if you think your UI thread has to do some busy processing that takes some time use Task.Run and let another thread do it

Another article that helped me: Async-Await by the brilliant explainer Stephen Cleary

查看更多
登录 后发表回答