1

Today I was reading about async/await best practices and run across this:

https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/blob/master/AsyncGuidance.md#async-void

Previously, for my fire-and-forget async methods, I was just calling the method and ignoring the returned Task:

DoSomethingAsync();

The compiler would produce a warning about the async method not being awaited, so I ended up adding a workaround to prevent it. There are several ways suggested here in StackOverflow, for instance by using a Task.Forget() extension method or a discard.

The article above, instead, suggests:

Task.Run(DoSomethingAsync);

I had never seen the Task.Run() overload that takes a Func<Task> argument. I looked up the documentation and found it confusing.

Assuming an async method is passed in (ie. defined as async Task DoSomethingAsync()), does that mean 2 tasks are created, one to run the async method and a proxy one that wraps the first? Or just 1 task?

In general, what's the use case for this overload? If a method already returns a Task, why not just await that?


EDIT: I don't feel like this is a duplicate of When correctly use Task.Run and when just async-await, but it looks very similar to await task.run vs await c#, which focuses on the Task.Run(Func<Task) overload. It doesn't explain in deep detail what happens in terms of tasks created and execution context differences though.

Zmaster
  • 1,095
  • 9
  • 23
  • `Task.Run` executes a delegate on a _background thread_. In case `DoSomethingAsync` is a pure asynchronous method, you may want to execute it on a background thread using the `Run(Func)` overload. – BionicCode Sep 27 '20 at 12:27
  • You should always `await` a `Task` either locally or return it to be awaited by the caller of the current method. – BionicCode Sep 27 '20 at 12:31
  • _"does that mean 2 tasks are created, one to run the async method and a proxy one that wraps the first?"_: Yes, that's correct. This proxy will implicitly unwrap the inner `Task` of the internal `Task` result. – BionicCode Sep 27 '20 at 12:35
  • 1
    Yeap, 2 tasks are created, and you are handed the outer (proxy) task. The reason that `Task.Run` accepts asynchronous delegates is because some methods that claim to be asynchronous (based on their signature), are partially or fully synchronous by their implementation. In the worst case they block the calling thread, and eventually return an already completed task. You can read [here](https://stackoverflow.com/questions/38739403/await-task-run-vs-await-c-sharp/58306020#58306020) my arguments in favor of using `Task.Run` in event handlers of GUI applications. – Theodor Zoulias Sep 27 '20 at 12:42
  • If an `async` method is not awaited it is executed on the same thread as the main thread. `Task.Run` on the other hand executes delegate on a Threadpool's thread using task manager which usually means a separate thread and that the main thead is not getting blocked by something like `Thread.Sleep();` – Fabjan Sep 27 '20 at 12:44
  • Actually, no. Three tasks are created, the outer `Task`, the inner `Task`, and the [proxy](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskextensions.unwrap) that combines the two into one. – Theodor Zoulias Sep 27 '20 at 12:51
  • People mark questions as duplicates of quasi-similar questions, without considering something important: Is it possible for an answer of this question to not be a valid answer of the other question? If the answer is yes, then the questions are different, and should not be marked as duplicates. Regarding this question, if I write an answer about using `Task.Run` for suppressing compiler warnings, and about how many tasks are created internally by `Task.Run(async...`, and then post it as an answer of the [duplicate](https://stackoverflow.com/questions/18013523), it would be an off topic post. – Theodor Zoulias Sep 27 '20 at 14:00
  • 1
    The provided link does not answer the purpose or implementation details of the special `Task.Run(Func)` overload. – BionicCode Sep 27 '20 at 15:21
  • 1
    `Task.Run(MyAsyncDelegate:Task):Task` simply does this: `Task.Factory.StartNew(MyAsyncDelegate):Task`. Then basically returns `Task.Unwrap():Task` to the caller. – BionicCode Sep 27 '20 at 15:27
  • At least the duplicate link answers _"If a method already returns a Task, why not just await that?"_. – BionicCode Sep 27 '20 at 15:33

0 Answers0