Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Any difference between "await Task.Run(); return;" and "return Task.Run()"?

Tags:

c#

async-await

Is there any conceptual difference between the following two pieces of code:

async Task TestAsync()  {     await Task.Run(() => DoSomeWork()); } 

and

Task TestAsync()  {     return Task.Run(() => DoSomeWork()); } 

Does the generated code differ, either?

EDIT: To avoid confusion with Task.Run, a similar case:

async Task TestAsync()  {     await Task.Delay(1000); } 

and

Task TestAsync()  {     return Task.Delay(1000); } 

LATE UPDATE: In addition to the accepted answer, there is also a difference in how LocalCallContext gets handled: CallContext.LogicalGetData gets restored even where there is no asynchrony. Why?

like image 245
avo Avatar asked Jan 09 '14 23:01

avo


People also ask

Should I await or return task?

So when you hit the await , the control flow is returned to the calling method and execution of your async method is resumed after the await when the awaited Task has finished. As there is no more code after your await , there is no need to use await anyway. Simply return the Task is enough.

What happens when a method returns a task without awaiting it?

An exception that's raised in a method that returns a Task or Task<TResult> is stored in the returned task. If you don't await the task or explicitly check for exceptions, the exception is lost. If you await the task, its exception is rethrown. As a best practice, you should always await the call.

Is .result same as await?

await asynchronously unwraps the result of your task, whereas just using Result would block until the task had completed. See this explanantion from Jon Skeet.

What is the return type of task in C#?

The recommended return type of an asynchronous method in C# is Task. You should return Task<T> if you would like to write an asynchronous method that returns a value. If you would like to write an event handler, you can return void instead. Until C# 7.0 an asynchronous method could return Task, Task<T>, or void.


2 Answers

One major difference is in exception propagation. An exception, thrown inside an async Task method, gets stored in the returned Task object and remains dormant until the task gets observed via await task, task.Wait(), task.Result or task.GetAwaiter().GetResult(). It is propagated this way even if thrown from the synchronous part of the async method.

Consider the following code, where OneTestAsync and AnotherTestAsync behave quite differently:

static async Task OneTestAsync(int n) {     await Task.Delay(n); }  static Task AnotherTestAsync(int n) {     return Task.Delay(n); }  // call DoTestAsync with either OneTestAsync or AnotherTestAsync as whatTest static void DoTestAsync(Func<int, Task> whatTest, int n) {     Task task = null;     try     {         // start the task         task = whatTest(n);          // do some other stuff,          // while the task is pending         Console.Write("Press enter to continue");         Console.ReadLine();         task.Wait();     }     catch (Exception ex)     {         Console.Write("Error: " + ex.Message);     } } 

If I call DoTestAsync(OneTestAsync, -2), it produces the following output:

 Press enter to continue Error: One or more errors occurred.await Task.Delay Error: 2nd 

Note, I had to press Enter to see it.

Now, if I call DoTestAsync(AnotherTestAsync, -2), the code workflow inside DoTestAsync is quite different, and so is the output. This time, I wasn't asked to press Enter:

 Error: The value needs to be either -1 (signifying an infinite timeout), 0 or a positive integer. Parameter name: millisecondsDelayError: 1st 

In both cases Task.Delay(-2) throws at the beginning, while validating its parameters. This might be a made-up scenario, but in theory Task.Delay(1000) may throw too, e.g., when the underlying system timer API fails.

On a side note, the error propagation logic is yet different for async void methods (as opposed to async Task methods). An exception raised inside an async void method will be immediately re-thrown on the the current thread's synchronization context (via SynchronizationContext.Post), if the current thread has one (SynchronizationContext.Current != null). Otherwise, it will be re-thrown via ThreadPool.QueueUserWorkItem). The caller doesn't have a chance to handle this exception on the same stack frame.

I posted some more details about TPL exception handling behaviour here and here.


Q: Is it possible to mimic the exception propagation behavior of async methods for non-async Task-based methods, so that the latter doesn't throw on the same stack frame?

A: If really needed, then yes, there is a trick for that:

// async async Task<int> MethodAsync(int arg) {     if (arg < 0)         throw new ArgumentException("arg");     // ...     return 42 + arg; }  // non-async Task<int> MethodAsync(int arg) {     var task = new Task<int>(() =>      {         if (arg < 0)             throw new ArgumentException("arg");         // ...         return 42 + arg;     });      task.RunSynchronously(TaskScheduler.Default);     return task; } 

Note however, under certain conditions (like when it's too deep on the stack), RunSynchronously could still execute asynchronously.


Another notable difference is that the async/await version is more prone to dead-locking on a non-default synchronization context. E.g., the following will dead-lock in a WinForms or WPF application:
static async Task TestAsync() {     await Task.Delay(1000); }  void Form_Load(object sender, EventArgs e) {     TestAsync().Wait(); // dead-lock here } 

Change it to a non-async version and it won't dead-lock:

Task TestAsync()  {     return Task.Delay(1000); } 

The nature of the dead-lock is well explained by Stephen Cleary in his blog.

like image 116
noseratio Avatar answered Oct 16 '22 06:10

noseratio


What is the difference between

async Task TestAsync()  {     await Task.Delay(1000); } 

and

Task TestAsync()  {     return Task.Delay(1000); } 

?

I am confused by this question. Let me try to clarify by responding to your question with another question. What's the difference between?

Func<int> MakeFunction() {     Func<int> f = ()=>1;     return ()=>f(); } 

and

Func<int> MakeFunction() {     return ()=>1; } 

?

Whatever the difference between my two things is, the same difference is between your two things.

like image 22
Eric Lippert Avatar answered Oct 16 '22 08:10

Eric Lippert