1

Say we have the following two methods inside a standard mvc controller.

public class SomeController : Controller
{
    public async Task<Response> SomeAction1()
        => await _someService.SomeAsyncMethod();

    public Task<Response> SomeAction2()
        => _someService.SomeAsyncMethod();

    // SomeAsyncMethod might look like this.
    // public async Task<Response> SomeAsyncMethod()
    //    => await someDbAction();
}

Simple question: does the base controller class execute a task as awaited/async?

Alternately do both of these actions do the exact same thing?

-- Edit for a bit of clarification. --

https://learn.microsoft.com/en-us/aspnet/mvc/overview/performance/using-asynchronous-methods-in-aspnet-mvc-4

How Requests Are Processed by the Thread Pool

On the web server, the .NET Framework maintains a pool of threads that are used to service ASP.NET requests. When a request arrives, a thread from the pool is dispatched to process that request. If the request is processed synchronously, the thread that processes the request is busy while the request is being processed, and that thread cannot service another request.

My goal is to understand if a method returning a Task needs to be accompanied with the word async. If the keyword is not there will it treat the method as synchronous and hold up the thread even though SomeAsyncMethod has an await in it creating the callback?

Community
  • 1
  • 1
Spaceman
  • 1,319
  • 13
  • 42
  • With `async/await` it will service your request, return to threadpool to service other requests and resume your action when async operation completes. This means you can do other things after the `async`. This also means if your `async` operation failed, you can get `Exception`s. With the second where you return the `Task`, it will be a fire and forget action. Meaning you basically not caring about the result of the `async` operation. – JohanP Feb 08 '17 at 03:39

2 Answers2

3

do both of these actions do the exact same thing?

Not exactly. Although they both have the same effect. Whenever you mark a method as async and have one or more await expressions in the method body, the compiler will generate a state machine for resuming the method asynchronously after the await call.

However, since the method body is a single method call that returns Task<Action>, all that compiler machinery is useless, and returning the Task directly will result in smaller compiled code.

At runtime you probably won't notice a difference, except maybe for a slightly higher execution time for the async Task<Action> case.

rossipedia
  • 56,800
  • 10
  • 90
  • 93
  • I think your answer is correct but its not what im looking for i have updated the question for clarity. – Spaceman Feb 08 '17 at 03:28
1

I made two simplified test methods using await and just executing and returning the Task:

private static async Task<int> TestAwait()
{
    await Task.Delay(1000);
    return 0;
}

private static Task Test()
{
    return Task.Delay(1000);
}

The IL generated for the two are quite different:

TestAwait:
IL_0000:  newobj      UserQuery+<TestAwait>d__0..ctor
IL_0005:  stloc.0     
IL_0006:  ldloc.0     
IL_0007:  call          System.Runtime.CompilerServices.AsyncTaskMethodBuilder<System.Int32>.Create
IL_000C:  stfld       UserQuery+<TestAwait>d__0.<>t__builder
IL_0011:  ldloc.0     
IL_0012:  ldc.i4.m1   
IL_0013:  stfld       UserQuery+<TestAwait>d__0.<>1__state
IL_0018:  ldloc.0     
IL_0019:  ldfld       UserQuery+<TestAwait>d__0.<>t__builder
IL_001E:  stloc.1     
IL_001F:  ldloca.s    01 
IL_0021:  ldloca.s    00 
IL_0023:  call        System.Runtime.CompilerServices.AsyncTaskMethodBuilder<System.Int32>.Start<<TestAwait>d__0>
IL_0028:  ldloc.0     
IL_0029:  ldflda      UserQuery+<TestAwait>d__0.<>t__builder
IL_002E:  call        System.Runtime.CompilerServices.AsyncTaskMethodBuilder<System.Int32>.get_Task
IL_0033:  ret

<TestAwait>d__0.MoveNext:
IL_0000:  ldarg.0     
IL_0001:  ldfld       UserQuery+<TestAwait>d__0.<>1__state
IL_0006:  stloc.0     
IL_0007:  ldloc.0     
IL_0008:  brfalse.s   IL_000C
IL_000A:  br.s        IL_000E
IL_000C:  br.s        IL_004C
IL_000E:  nop         
IL_000F:  ldc.i4      E8 03 00 00 
IL_0014:  call        System.Threading.Tasks.Task.Delay
IL_0019:  callvirt    System.Threading.Tasks.Task.GetAwaiter
IL_001E:  stloc.2     
IL_001F:  ldloca.s    02 
IL_0021:  call        System.Runtime.CompilerServices.TaskAwaiter.get_IsCompleted
IL_0026:  brtrue.s    IL_0068
IL_0028:  ldarg.0     
IL_0029:  ldc.i4.0    
IL_002A:  dup         
IL_002B:  stloc.0     
IL_002C:  stfld       UserQuery+<TestAwait>d__0.<>1__state
IL_0031:  ldarg.0     
IL_0032:  ldloc.2     
IL_0033:  stfld       UserQuery+<TestAwait>d__0.<>u__1
IL_0038:  ldarg.0     
IL_0039:  stloc.3     
IL_003A:  ldarg.0     
IL_003B:  ldflda      UserQuery+<TestAwait>d__0.<>t__builder
IL_0040:  ldloca.s    02 
IL_0042:  ldloca.s    03 
IL_0044:  call        System.Runtime.CompilerServices.AsyncTaskMethodBuilder<System.Int32>.AwaitUnsafeOnCompleted<TaskAwaiter,<TestAwait>d__0>
IL_0049:  nop         
IL_004A:  leave.s     IL_00AB
IL_004C:  ldarg.0     
IL_004D:  ldfld       UserQuery+<TestAwait>d__0.<>u__1
IL_0052:  stloc.2     
IL_0053:  ldarg.0     
IL_0054:  ldflda      UserQuery+<TestAwait>d__0.<>u__1
IL_0059:  initobj     System.Runtime.CompilerServices.TaskAwaiter
IL_005F:  ldarg.0     
IL_0060:  ldc.i4.m1   
IL_0061:  dup         
IL_0062:  stloc.0     
IL_0063:  stfld       UserQuery+<TestAwait>d__0.<>1__state
IL_0068:  ldloca.s    02 
IL_006A:  call        System.Runtime.CompilerServices.TaskAwaiter.GetResult
IL_006F:  nop         
IL_0070:  ldloca.s    02 
IL_0072:  initobj     System.Runtime.CompilerServices.TaskAwaiter
IL_0078:  ldc.i4.0    
IL_0079:  stloc.1     
IL_007A:  leave.s     IL_0096
IL_007C:  stloc.s     04 
IL_007E:  ldarg.0     
IL_007F:  ldc.i4.s    FE 
IL_0081:  stfld       UserQuery+<TestAwait>d__0.<>1__state
IL_0086:  ldarg.0     
IL_0087:  ldflda      UserQuery+<TestAwait>d__0.<>t__builder
IL_008C:  ldloc.s     04 
IL_008E:  call        System.Runtime.CompilerServices.AsyncTaskMethodBuilder<System.Int32>.SetException
IL_0093:  nop         
IL_0094:  leave.s     IL_00AB
IL_0096:  ldarg.0     
IL_0097:  ldc.i4.s    FE 
IL_0099:  stfld       UserQuery+<TestAwait>d__0.<>1__state
IL_009E:  ldarg.0     
IL_009F:  ldflda      UserQuery+<TestAwait>d__0.<>t__builder
IL_00A4:  ldloc.1     
IL_00A5:  call        System.Runtime.CompilerServices.AsyncTaskMethodBuilder<System.Int32>.SetResult
IL_00AA:  nop         
IL_00AB:  ret         

<TestAwait>d__0.SetStateMachine:
IL_0000:  ret         

<TestAwait>d__0..ctor:
IL_0000:  ldarg.0     
IL_0001:  call        System.Object..ctor
IL_0006:  nop         
IL_0007:  ret         

-------------- END OF TESTAWAIT

Test:
IL_0000:  nop         
IL_0001:  ldc.i4      E8 03 00 00 
IL_0006:  call        System.Threading.Tasks.Task.Delay
IL_000B:  stloc.0     
IL_000C:  br.s        IL_000E
IL_000E:  ldloc.0     
IL_000F:  ret

So the amount of code the compiler generates is quite impressive for TestAwait

JohanP
  • 5,252
  • 2
  • 24
  • 34