37

I've searched the web and seen a lot 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!

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
Mattias
  • 2,929
  • 5
  • 29
  • 46
  • 2
    `Task.Run` allows you to await an synchronous method. If your method is already asynchronous and returns a Task then you don't need it – Matt Wilko Aug 03 '16 at 09:20
  • 2
    The two snippets are *not* equivalent. The first line won't wait for `LongProcess()` to complete, it will simply fire a child task and return. To make them equivalent you'd need to use `await LongProcess()` – Panagiotis Kanavos Aug 03 '16 at 09:20
  • 10
    @PanagiotisKanavos Not true. The code exactly as posted will use the `Func` overload of `Task.Run`, so it *will* wait for `LongProcess` to finish. – Luaan Aug 03 '16 at 09:22
  • There's a duplicate question somewhere, but I can't find it. – Dennis_E Aug 03 '16 at 09:30

3 Answers3

27

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 case 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

Harald Coppoolse
  • 28,834
  • 7
  • 67
  • 116
23

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

Luaan
  • 62,244
  • 7
  • 97
  • 116
  • 2
    In this case the real difference is that in the first snippet, the synchronous code at the start of `LongProcess` will (may) run on a separate thread. In the second case it will definitely run on the caller thread – Panagiotis Kanavos Aug 03 '16 at 09:23
  • So `await Task.Run(() => { process.Start(); process.WaitForExit(); } );` for uploading a big file won't block the thread? – Toolkit Aug 12 '19 at 06:59
  • @Toolkit It will not block the caller thread; whether that's actually a useful thing depends on the context. For example, in a Windows Forms application, it will introduce reentrancy. That allows for more responsive GUI, but is also harder to reason about. Make sure your invariants are actually invariant, and it's usually worth it, though. You're also borrowing a thread from the pool for no reason, though - instead of `Task.Run` and `WaitForExit`, just do an asynchronous wait on the process (either through the `Exited` event, or just waiting on the handle). – Luaan Aug 12 '19 at 07:08
  • @Luaan that's actually a Console app that is supposed to run both on Windows and Linux and it uploads files sometimes to remote Linux server. So I am using `bash` from `cygwin` and `scp` command. I guess they will block the thread no matter current or new one? So no point even doing `await Task.Run`? – Toolkit Aug 12 '19 at 07:39
  • 1
    @Toolkit Do you have something else to do with that thread? There's little point in writing asynchronous code if what you're doing is perfectly synchronous anyway :) – Luaan Aug 12 '19 at 07:46
  • @Luaan right lol, there is no such thing as as async file transfer :) Ok thanks – Toolkit Aug 12 '19 at 08:10
  • 1
    @Toolkit Well, it's not really that it's impossible; but it's not really the standard with Unix tools, which are usually very simple single-threaded tools that do one job, and integrate with other simple Unix tools. It wouldn't be weird for a Windows GUI application to upload ten files to ten different servers in parallel, for example (and just by having a GUI, you benefit from keeping the copying asynchronous with respect to the UI); on Unix, you'd usually just spawn ten different copying processes simultaneously. – Luaan Aug 12 '19 at 08:13
  • By not truly asynchronous are you referring to cpu bound tasks? – variable Jul 23 '21 at 14:57
  • @variable A truly asynchronous function would be one that (almost) immediately returns the task to be awaited; if you have reason to believe that will not be the case for whatever reason (e.g. sleeps, waits, synchronous I/O, CPU work...), you as the caller will have to wait for that to finish before you get control back. In that case, using a `Task.Run` will give you control back (almost) immediately, regardless of what the callee does. – Luaan Jul 23 '21 at 18:19
  • Without Task.Run does async/await run on the same thread? – variable Jul 28 '21 at 15:19
  • 1
    @variable It's a bit trickier than that. A task might represent an asynchronous I/O task, for example - in which case there is _no_ thread. It might represent a bunch of different tasks, which might mean multiple threads. It might require marshalling back to e.g. an UI thread, in which case it will bottleneck with everything else happening on the UI thread. Threads are way lower level concepts than tasks or await. Instead, think about what is allowed to run synchronously with respect to what. E.g. if you await a truly async function from the UI thread, the UI thread is free to do other things. – Luaan Aug 01 '21 at 06:53
  • @variable Essentially, a `Task.Run` will almost guarantee that the caller will continue immediately. From the point of view of this one synchronous workflow you're doing (connected through consecutive awaits), this makes no difference. But it absolutely can have side-effects somewhere else (e.g. does the UI remain responsive?) and if you ever break the proper await chain (i.e. there's a place where you _don't_ await an async function), things can get really confusing really fast. As is usually the case, the borders are the trickiest. – Luaan Aug 01 '21 at 06:56
11

This answer deals with the specific case of awaiting an async method in the event handler of a GUI application. In this case the first approach has a significant advantage over the second. Before explaining why, lets rewrite the two approaches in a way that reflects clearly the context of this answer. What follows is only relevant for event handlers of GUI applications.

private async void Button1_Click(object sender, EventArgs args)
{
    await Task.Run(async () => await LongProcessAsync());
}

vs

private async void Button1_Click(object sender, EventArgs args)
{
    await LongProcessAsync();
}

I added the suffix Async in the method's name, to comply with the guidlines. I also made async the anonymous delegate, just for readability reasons. The overhead of creating a state machine is minuscule, and is dwarfed by the value of communicating clearly that this Task.Run returns a promise-style Task, not an old-school delegate Task intended for background processing of CPU-bound workloads.

The advantage of the first approach is that guarantees that the UI will remain responsive. The second approach offers no such guarantee. As long as you are using the build-in async APIs of the .NET platform, the probability of the UI being blocked by the second approach is pretty small. After all, these APIs are implemented by experts¹. By the moment you start awaiting your own async methods, all guarantees are off. Unless of course your first name is Stephen, and your surname is Toub or Cleary. If that's not the case, it is quite possible that sooner or later you'll write code like this:

public static async Task LongProcessAsync()
{
    TeenyWeenyInitialization(); // Synchronous
    await SomeBuildInAsyncMethod().ConfigureAwait(false); // Asynchronous
    CalculateAndSave(); // Synchronous
}

The problem obviously is with the method TeenyWeenyInitialization(). This method is synchronous, and comes before the first await inside the body of the async method, so it won't be awaited. It will run synchronously every time you call the LongProcessAsync(). So if you follow the second approach (without Task.Run), the TeenyWeenyInitialization() will run on the UI thread.

How bad this can be? The initialization is teeny-weeny after all! Just a quick trip to the database to get a value, read the first line of a small text file, get a value from the registry. It's all over in a couple of milliseconds. At the time you wrote the program. In your PC. Before moving the data folder in a shared drive. Before the amount of data in the database became huge.

But you may get lucky and the TeenyWeenyInitialization() remains fast forever, what about the second synchronous method, the CalculateAndSave()? This one comes after an await that is configured to not capture the context, so it runs on a thread-pool thread. It should never run on the UI thread, right? Wrong. It depends to the Task returned by SomeBuildInAsyncMethod(). If the Task is completed, a thread switch will not occur, and the CalculateAndSave() will run on the same thread that called the method. If you follow the second approach, this will be the UI thread. You may never experience a case where the SomeBuildInAsyncMethod() returned a completed Task in your development environment, but the production environment may be different in ways difficult to predict.

Having an application that performs badly is unpleasant. Having an application that performs badly and freezes the UI is even worse. Do you really want to risk it? If you don't, please use always Task.Run(async inside your event handlers. Especially when awaiting methods you have coded yourself!

¹ Disclaimer, some built-in async APIs are not properly implemented.


Important: The Task.Run runs the supplied asynchronous delegate on a ThreadPool thread, so it's required that the LongProcessAsync has no affinity to the UI thread. If it involves interaction with UI controls, then the Task.Runis not an option. Thanks to @Zmaster for pointing out this important subtlety in the comments.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
  • I like this answer, easy to understand and you make some good points. I have a couple of observations: 1) `Task.Run(async () => await LongProcessAsync())` would be equivalent to `Task.Run(LongProcessAsync)`, at least from the point of view of Button1_Click and LongProcessAsync, correct? 2) With Task.Run(), code in LongProcessAsync would be running on a new thread, right? That means LongProcessAsync could no longer update UI for instance (assuming that can only be done from the main thread). So, that's something to consider before using Task.Run() everywhere. – Zmaster Sep 27 '20 at 13:35
  • 1
    @Zmaster yeap, you are absolutely right at both points! I will consider editing my post to address the second point, which is quite important. – Theodor Zoulias Sep 27 '20 at 13:41
  • Question… This says you can no longer update the UI because you’re on a different thread. But there’s nothing stopping you from dispatching back to the UI thread, is there? That seemingly should be completely allowed, correct? If not, can you explain why not? – Mark A. Donohoe Dec 26 '20 at 07:15
  • @MarkA.Donohoe yes, it is possible to dispatch work to the UI thread while you are doing background processing, but this is architecturally suspicious IMHO. It means that your code may have become too entangled, and that the principal of separation of concerns may have been violated. It is probably preferable to handle these cases by using the `IProgress` abstraction. Let the background processor report progress in an abstract way, and let the caller translate these reports to actual UI modifications. – Theodor Zoulias Dec 26 '20 at 07:40
  • That’s actually the exact method I’m using. It just sounded above like it was saying a blanket statement that there was no way for a background thread to update the UI. I wasn’t sure if it put a restriction on progress. Glad to hear it doesn’t. – Mark A. Donohoe Dec 26 '20 at 07:41
  • @MarkA.Donohoe actually in the context of this answer, it doesn't make sense to use the dispatcher at all. If you have an asynchronous method that accesses UI controls, and you trust all awaited calls inside the method to be properly asynchronous (meaning that they don't block the UI thread), then everything is OK and you don't have to change anything. On the other hand if you don't trust some call, you could wrap only that call in a `Task.Run`. Provided of course that the suspicious call is not itself an asynchronous method that accesses UI controls internally! – Theodor Zoulias Dec 26 '20 at 07:52
  • Great info! I’ve since switched to `Progress` so that takes care of it for me. On a side-note, I ended up subclassing `Progress` because I didn’t like that you have to cast it to `IProgress` before you can call `Report` on it. That just seems odd to me. – Mark A. Donohoe Dec 26 '20 at 07:56
  • The intended usage of the `Progress` class is to instantiate it in the UI layer, and pass it as an argument in a method that has an `IProgress` parameter. This way the UI layer can handle incoming reports, but cannot report itself (at least not easily). And the data layer can send reports but cannot register handlers, which also makes sense. That said, the whole mechanism is lightweight and thin, and creating specialized implementations of the `IProgress` interface is quite easy. So you can certainly consider customizing it, if there is a reason. – Theodor Zoulias Dec 26 '20 at 15:28