12

If I have the following method:

public async Task<T> DoSomethingAsync<T>(Func<Task<T>> action)
{
   // bunch of async code..then "await action()"
}

What is the difference between the following two usages:

public async Task MethodOneAsync()
{
   return await DoSomethingAsync(async () => await SomeActionAsync());
}

public async Task MethodTwoAsync()
{
   return await DoSomethingAsync(() => SomeActionAsync());
}

Both compile, both work and there are no C# warnings.

What's the difference (if any)? Will both methods run true async if awaited by the caller?

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
RPM1984
  • 72,246
  • 58
  • 225
  • 350
  • Difference is that `MethodTwo` elides `async-await` and `MethodOne` is not. Don't waste time on arguing about "different" opinions ;). Stephen Cleary has an article about this difference [Eliding Async and Await](https://blog.stephencleary.com/2016/12/eliding-async-await.html) – Fabio Aug 23 '20 at 03:22

4 Answers4

1

There is no functional difference between the two. The only difference is if the Task from SomeActionAsync is returned directly or if it is awaited. Stephen Cleary has a good blog post about this, and recommends the second aproach for this trivial case.

The reason why the first approach is available is that you could have a non-trivial lambda expression like this:

public async Task MethodOneAsync()
{
    return await DoSomethingAsync(async () => {
        var i = _isItSunday ? 42 : 11;
        var someResult = await SomeActionAsync(i);
        return await AnotherActionAsync(someResult*i);
    });
}

So the difference is the same as the difference between a method whith this signature public async Task<int> MyMethod and this one public Task<int> MyMethod

Joakim M. H.
  • 424
  • 4
  • 14
  • What prevents a user from wrapping the curly bracket logic into a separate method and use Async Await, this is not the reason async await is provided here, this is completely diminishing the value of Async-Await. Purpose of Async - Await is always a non blocking operation, where a long running operation can be made to run in background, as OP has confirmed that functionally two are same and they are indeed, difference will only be in the performance for a long running method and that's why Async Await is preferred – Mrinal Kamboj Sep 27 '19 at 03:57
  • Also let me add Compute based Async operation always runs on threadpool even with Async -Await, there's no other way, just that the main calling thread is free. Genuine Async operation are mostly IO operations as they don't need any thread and genuinely runs in the background – Mrinal Kamboj Sep 27 '19 at 03:59
  • so is the answer that not awaiting would swallow the exception , and only the outer code would have to handle? need someone to just paraphrase what the diff is in a nutshell. – Seabizkit Feb 03 '20 at 12:22
  • @Seabizkit What exception? – Joakim M. H. Feb 03 '20 at 15:08
  • 1
    @JoakimM.H. read https://blog.stephencleary.com/2016/12/eliding-async-await.html – Seabizkit Feb 03 '20 at 15:39
  • @Seabizkit Yes, that's the blog post I linked to in my answer. I was just confused since there is no exception in my answer. The only functional difference is error handling, but an exception is never swallowed. The difference is if an exception is thrown when you call the method or when you await its task - as explained by Stephen Cleary. – Joakim M. H. Feb 04 '20 at 08:14
  • ok yes.... i think we saying the same same, when i was saying swallowed, i was meaning that if there were is logic in the delegate action for exception (handling) then that would be skipped and only parent handling would catch it. so in sense the difference is that if your delegate action has exception handling it would be ignored if you dont use `async () => await` correct? – Seabizkit Feb 04 '20 at 08:25
  • I'm not quite sure I understand what you're asking. Perhaps you should post a new question where you could elaborate. If you have error handling in your lambda, that's not the same trivial case as in the original question. – Joakim M. H. Feb 04 '20 at 08:44
0

Short Answer

MethodOneAsync() is truly Async, and shall be used, but MethodTwoAsync() is not truly Async, as it invokes Thread pool thread

Long Answer

For the purpose of testing and running I have simplified your code as follows:

Execution from the Main method of Linqpad as follows:

var resultTask = MethodOneAsync(); // Comment one the methods

resultTask.Result.Dump();

Actual Code

public async Task<int> DoSomethingAsync(Func<Task<int>> action)
{
    return await Task.FromResult<int>(3);
}

public async Task<int> MethodOneAsync()
{
    await Task.Delay(10);
    return await DoSomethingAsync(async () => await Task.FromResult<int>(3));
}

public async Task<int> MethodOneAsync()
{
    await Task.Delay(10);
    return await DoSomethingAsync(() => Task.FromResult<int>(3));
}

Now I reviewed the IL generated between the two calls and following is the most important difference:

First call with Async and Await inside DoSomethingAsync has following IL:

<>c.<MethodOneAsync>b__2_0:
IL_0000:  newobj      UserQuery+<>c+<<MethodOneAsync>b__2_0>d..ctor
IL_0005:  stloc.0     
IL_0006:  ldloc.0     
IL_0007:  ldarg.0     
IL_0008:  stfld       UserQuery+<>c+<<MethodOneAsync>b__2_0>d.<>4__this
IL_000D:  ldloc.0     
IL_000E:  call        System.Runtime.CompilerServices.AsyncTaskMethodBuilder<System.Int32>.Create
IL_0013:  stfld       UserQuery+<>c+<<MethodOneAsync>b__2_0>d.<>t__builder
IL_0018:  ldloc.0     
IL_0019:  ldc.i4.m1   
IL_001A:  stfld       UserQuery+<>c+<<MethodOneAsync>b__2_0>d.<>1__state
IL_001F:  ldloc.0     
IL_0020:  ldfld       UserQuery+<>c+<<MethodOneAsync>b__2_0>d.<>t__builder
IL_0025:  stloc.1     
IL_0026:  ldloca.s    01 
IL_0028:  ldloca.s    00 
IL_002A:  call        System.Runtime.CompilerServices.AsyncTaskMethodBuilder<System.Int32>.Start<<<MethodOneAsync>b__2_0>d>
IL_002F:  ldloc.0     
IL_0030:  ldflda      UserQuery+<>c+<<MethodOneAsync>b__2_0>d.<>t__builder
IL_0035:  call        System.Runtime.CompilerServices.AsyncTaskMethodBuilder<System.Int32>.get_Task
IL_003A:  ret      

Second one without Async and Await has the following code:

<>c.<MethodOneAsync>b__2_0:
IL_0000:  ldc.i4.3    
IL_0001:  call        System.Threading.Tasks.Task.FromResult<Int32>
IL_0006:  ret      

Beside this the first one has the complete state machine code for extra async await call, which is expected.

Important points:

  1. For Async method call use async () => await SomeActionAsync(), as this is true Async execution and works on IO completion ports
  2. In other case it invokes a Threadpool thread for the Async method execution, which is not good for Asynchronous execution

I can paste the complete IL if required to understand the difference, but best is you evaluate the same in the Visual studio or LinqPad to understand the nuances

Mrinal Kamboj
  • 11,300
  • 5
  • 40
  • 74
  • 1
    @StephenCleary any chance you could make a comment on this answer, please? – Pure.Krome Oct 17 '16 at 00:09
  • 1
    @Pure.Krome, thanks for your comment, down-vote is never an issue, but justification is important, so that I can learn and correct. It shall never be an whimsical. – Mrinal Kamboj Oct 17 '16 at 07:14
  • The difference is not in wether or not a threadpool thread is involved. The only difference is if the task from SomeActionAsync() is returned directly, or if it is awaited. See [this blogpost](https://blog.stephencleary.com/2016/12/eliding-async-await.html) from Stephen Cleary. – Joakim M. H. Sep 24 '19 at 12:26
  • @JoakimM.H. What's is a `Task`, How does it run, does it needs Thread Pool thread. `Task` only use / share Threadpool threads. This question is not about eliding, that you have pointed out, what happens if `SomeActionAsync()` is a long running method, will there be any difference in performance between two implementations, does using Async explicitly helps it runs faster, that's where the difference is, and that's why Async is preferred. Before you consider down-voting atleast try and have a discussion to understand the exact perspective – Mrinal Kamboj Sep 27 '19 at 03:51
  • i feel like this is probably the answer but could the examples been shown actually use the delegate passed as well... affirm usage. as in either case the delegate would still need to be awaited if its result is needed... which then make understanding the point of the other syntax difficult to see what it would be doing. Passing a async method is the same in both cases correct? and both would could be awaited in the method, so is `async () => await` used to pass the synchronization context. – Seabizkit Feb 01 '20 at 08:43
  • @Seabizkit please understand this answer differently, Synchronization context is just meant for re-entry and here we are not defining it explicitly. Difference between 2 pieces of code is `Func`, which creates the Task, which can be awaited is in itself an Async implementation in the first one, now let's assume, that is a IO call, which is where Async implementation will make a difference by relieving the original thread, though in second one it would be a blocking operation. Only IL stack review can help us understand this issue, otherwise functionally wise they are exactly same – Mrinal Kamboj Feb 02 '20 at 15:31
  • 1
    I'm sorry, but this is wrong. Wether or not the lambda argument to DoSomethingAsync is async or not has nothing to do with blocking or non-blocking. Let's assume that DoSomethingAsync and SomeActionAsync are properly implemented async methods: the difference is wether or not the lambda returns the Task from SomeActinAsync directly, or if it wraps the result in a new Task and returns that new Task. The generated IL is different because the async lambda creates a redundant state machine. – Joakim M. H. Feb 04 '20 at 08:21
  • Lambda argument (Func) can deliver the Task only on its completion. Now how shall we wait for that completion, in an async or sync manner, makes all the difference here. Try making a long running 100+ IO call from inside the lambda / func and difference in scalability will be easy to identify. Not sure where did came across the concept of redundant state machine, that is one thing IL will optimize if it finds its redundant, having generating the state machine, it clearly points out the difference between two. IL is much more optimized than we assume. – Mrinal Kamboj Feb 04 '20 at 08:38
  • That is not true. The lambda can return the Task from SomeActionAsync regardless of its completion state. – Joakim M. H. Feb 04 '20 at 09:09
  • It's not just about compilation but execution which makes the final difference in approach, yes it will compile as `Task `, that's why both approach look same in theory and you assumed eliding, when IL doesn't suggest. IL is different means we are treating them differently. Try calling a long running IO call in synchronous manner and difference would be clear. There's no point guessing and calling state machine redundant – Mrinal Kamboj Feb 04 '20 at 09:50
  • What you're saying makes no sense. And your examples are flawed. The lambda arguments are never used. If I understand you correctly, you keep insisting that SomeActionAsync (from OPs code) will execute synchronously if you do not have the `await` keyword before it. That's simply not the case (assuming that SomeActionAsync is properly implemented as async). Consider this lambda: `async () => {Task t = SomeActionAsync(); await t;}`, the lambda uses async-await but SomeActionAsync does not have `async` directly in front if it. Is it async? – Joakim M. H. Feb 04 '20 at 10:08
  • In the original example it's done in single line, you may replace it with your multi line implementation, result doesn't change as you are also explicitly awaiting the Task within Func.Difference is in original example one of the implementation doesn't await the task within Func, which means calling thread is never relieved as expected in Async and Func will return only on completion of Task, thus blocking calling thread. Op Codes you are pointing to is IL you can generate. On a scalable system, execute your IO without `await` within Func and see the impact, why waste time commenting – Mrinal Kamboj Feb 04 '20 at 10:39
  • Also your multi line implementation is incorrect, you have to explicitly return the result inside the Lambda for it to compile. Only single line works without return. Not sure what part of my comments create confusion, but this is not the case for eliding, since long running call could be wrapped inside a Func, which would block the execution of Func which is further awaited outside – Mrinal Kamboj Feb 04 '20 at 10:47
  • 2
    `Difference is in original example one of the implementation doesn't await the task within Func, which means calling thread is never relieved as expected in Async and Func will return only on completion of Task, thus blocking calling thread.` That is simply not how it works. The task is simply returned. To block, you would have to do something like `SomeActionAsync().Result`. – Joakim M. H. Feb 04 '20 at 11:52
  • And you think `Task.Result` and `await Task` can be used interchangeably, then good luck with your design. `Task.Result` or `Task.Wait` are probably the worst thing for a long running Async call – Mrinal Kamboj Feb 04 '20 at 13:59
  • Ok, I think we've got a language barrier here and we won't get any further. – Joakim M. H. Feb 17 '20 at 07:34
0

Firstly, your example doesn't make sense, you're either returning something, or not, returning the results of the awaited function without a return type would be a compiler error.

public async Task MethodOneAsync()
{
    return await DoSomethingAsync(() => SomeActionAsync());
}

Secondly, this has nothing to do with "true" async as that would be an implementation detail which is not shown

Thirdly, the only difference between either of the hypothetical examples

await DoSomethingAsync(async () => await SomeActionAsync());

and

await DoSomethingAsync(() =>  SomeActionAsync()); 

Given the definition of DoSomethingAsync, in the first example the compiler will create an extra IAsyncStateMachine implementation, instead of just forwarding the Task. I.e More compiled code, more IL, more instructions, and in this example seemingly redundant.

There are is a small caveat with exceptions when eliding tasks, however because this is just a simple pass through there is no other code which will throw, ergo the extra state-machine or try catch and Task.FromException is not needed.

The real appreciable differences would come if your signature was actually an Action instead of Func<Task> which would create an async void given an async lambda, however this is not case in your question.

TheGeneral
  • 79,002
  • 9
  • 103
  • 141
  • You first comment believe you are wrong about waiting a "void" you should always return task even if its a void.(i could be wrong). Your answer is the most interesting so far. Still not getting what the difference is other than perhaps another wrapper of task. Perhaps have you found anything else explain the diff in more details seems very odd. its like your passing await as part of the delegate but that doesn't make much sense either. – Seabizkit Feb 01 '20 at 08:31
-1

Async/await construct inserts some infrastructure code which is only useful if there is some code after the "await". Otherwise it essentially does nothing. Your code is equivalent to

public Task MethodThreeAsync()
{
    return DoSomethingAsync(() => SomeActionAsync());
}

All of the three methods are "true async".

dvorn
  • 3,107
  • 1
  • 13
  • 12
  • 2
    That infrastructure code generated by an `await` call also ensures a stack frame in exception reports. If you rely on `Exception.StackTrace` for debugging, you should use `await`, rather than just returning tasks. – yaakov Oct 14 '16 at 05:55
  • This is incorrect, it is not mere Infrastructure code as shown in my answer, it is difference between true Async and Thread pool based for concurrency, which create whole lot of difference for the application and its working – Mrinal Kamboj Oct 14 '16 at 09:42
  • Not sure why you're downvoted. This is mostly correct except for some error-handling gotchas. – Joakim M. H. Feb 04 '20 at 12:47