5

Suppose I have a method that runs a continuous while loop with some async calls

async Task MethodA(){
    while(true){ perform async/await operations }
}

what is the difference between:

Task.Run( () => MethodA(); }
Task.Run( async () => await MethodA(); }

And if there is a difference, when is one more useful than the other? Does Task.Run treat each of its overloads differently?

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
cubesnyc
  • 1,385
  • 2
  • 15
  • 32
  • 2
    Does this answer your question? [What is the purpose of "return await" in C#?](https://stackoverflow.com/questions/19098143/what-is-the-purpose-of-return-await-in-c) – Christophe Devos Jul 23 '20 at 13:35
  • The question about eliding async-await has been asked [dozens of times](https://stackoverflow.com/questions/linked/19098143), and the arguments pros and cons are always the same. It's an important question, and asking it is OK, but there is no point at repeating the same arguments in multiple posts. This question is correctly marked as a duplicate. – Theodor Zoulias Jun 09 '23 at 23:15
  • @TheodorZoulias Have you actually read the threads listed in the link you provided? Just because they have Task and await in the prompt doesnt make the question a duplicate. There is a nuance to how Task.Run treats its overloads that goes beyond eliding async-await as CristopheDevos pointed out. – cubesnyc Jun 10 '23 at 01:10
  • Not all of them, but I have read dozens of similar questions. [Christophe Devos's](https://stackoverflow.com/a/63055660/11178549) answer goes beyond your question, and explains also the difference between `Task.Run( () => { MethodA(); } );` and `Task.Run( () => MethodA(); }`. Which has nothing to do with eliding async-await, and is also off-topic because you haven't asked about it. Your question is strictly about eliding async-await in an asynchronous delegate, and it's a question that has been beaten to death. There is nothing new to add to it. – Theodor Zoulias Jun 10 '23 at 01:44
  • @TheodorZoulias Again, for the third time, Task.Run operates differently depending on the overload you provide, even when just considering the two overloads in my original question. The intent of removing the flag is to erase the patently incorrect insinuation that the linked question is a duplicate, or that any question in the list you've provided is a duplicate. Had you actually read through any of them, you would have known that. – cubesnyc Jun 10 '23 at 02:32
  • In your question the exact same [`Task.Run`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.run) overload (`Task.Run(Func function)`) is invoked twice. The first invocation elides async-await, and the second is not. [Here](https://stackoverflow.com/questions/53807858) is one exact duplicate from the list, [here](https://stackoverflow.com/questions/61462219) is another one, and [here](https://stackoverflow.com/questions/75943463) is another one. Is this enough proof that I have read at least one question in the list with the duplicate questions? – Theodor Zoulias Jun 10 '23 at 02:56
  • I think whats really funsies, is that you linked me to three questions that were asked after mine, two of which were again incorrectly flagged as being duplicates, and the third which asks about how Task.WhenAll interacts with Task.Run. – cubesnyc Jun 10 '23 at 07:11
  • Personally I have nothing to gain or lose by your question being open or closed. I won't cast a reopen vote because it would be against my judgment, but if you manage to persuade the community that your question is unique and hasn't been answered before, so be it. In that case the probability of getting new quality answers is close to zero anyway. Stephen Cleary and Eric Lippert won't make their appearance, just to repeat the same pros and cons arguments about eliding async/await that are known for ages. – Theodor Zoulias Jun 10 '23 at 14:14

2 Answers2

2

According to answer of Stephen Cleary in answer https://stackoverflow.com/a/19098209/3107892, and his linked blog post, the only difference is that the second one creates a state machine. Here you can use the first case without any downside.

Here are some guidelines that you can follow:

  • Do not elide by default. Use the async and await for natural, easy-to-read code;
  • Do consider eliding when the method is just a passthrough or overload;
  • Do not elide if you want the method to show up in your stack trace.

Just to be complete, based on another answer here: Task.Run(Func<Task>) will queue the result of the function on the thread pool and the resulting function will finish when the queued task finishes (including all awaits)

Task.Run( MethodA ); will convert the method to a delegate and pass this on.

Task.Run( () => MethodA() ); will create a hidden method that will return the result of MethodA (it's this hidden method that will be converted to a delegate and passed on).

Task.Run( async () => await MethodA() ); will create a hidden method that contains the await, but as stated earlier, this will be converted to a state machine that in the end wraps the result of MethodA() in a new Task.

Task.Run( () => { MethodA(); } ); is something completely different, because of the curly brackets, this will call the Task.Run(Action) overload and in this case the MethodA() will run on this task until it hits the first uncompleted await and then finish. What happens after this inner task completes (after the inner await) will not be monitored by this Task.Run but will keep running on the thread pool and if it throws an exception, can cause your application to crash. The reason for this is that the task that MethodA returns is ignored due to the lack of the keyword return.

Task.Run( () => { return MethodA(); } ); is a fix for this exception and will work just as all the other examples.

-1

There is a considerable difference. MethodA() returns a Task. If you do not await the result of that Task then processing will continue as normal.

ie

Task.Run( () => MethodA(); }

MethodA() returns a Task as soon as it hits an await internally. The lambda is now complete (it has called MethodA() and got a return value), so Task.Run() will mark it's own task as complete and the thread will be released.

Task.Run( async () => await MethodA(); }

The lambda is async and you are awaiting the true result of MethodA(). The Task returned by Task.Run() will not complete until MethodA() is complete.

GazTheDestroyer
  • 20,722
  • 9
  • 70
  • 103
  • Your first response is wrong, the task returned by `Task.Run()` will not be marked as complete when the inner method hits it's first await. This lambda notation will return the `Task` from `MethodA()` to the `Task.Run()` and `Task.Run()` will only be marked complete when the `Task` that it has received (from `MethodA()`) will finish. (also passing trough any exceptions `MethodA()` throws at any time, before or after any await). – Christophe Devos Jun 12 '23 at 06:40