Quantcast
Channel: sfeldman.NET
Viewing all articles
Browse latest Browse all 96

Task vs async Task

$
0
0

How often do you write code and think about what will it look like the compiler is done with it? If you're like me, not often. But is it a good thing that over the time we've learned to trust unquestionably the compiler and blindly rely on it to do the job for us?

I was lucky to get some guidance from Daniel Marbach on async/await and the importance of understanding code optimizations that compiler is performing. Without any further due, let's dive into an example.

Consider the following method:

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

Now the same method with a slight variation, marking the method as async and awaiting the delay.

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

Looks almost identical. But is it? Let's look at what compiler generates.

For the first method, it's identical to the original code:

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

But for the second method, the compiler does... magic and voodoo.

private Task MainAsync2()
{
    UserQuery.\u003CMainAsync2\u003Ed__2 mainAsync2D2 = new UserQuery.\u003CMainAsync2\u003Ed__2();
    mainAsync2D2.\u003C\u003E4__this = this;
    mainAsync2D2.\u003C\u003Et__builder = AsyncTaskMethodBuilder.Create();
    mainAsync2D2.\u003C\u003E1__state = -1;
    AsyncTaskMethodBuilder taskMethodBuilder = mainAsync2D2.\u003C\u003Et__builder;
    ((AsyncTaskMethodBuilder) @taskMethodBuilder).Start<UserQuery.\u003CMainAsync2\u003Ed__2>((M0&) @mainAsync2D2);
    return ((AsyncTaskMethodBuilder) @mainAsync2D2.\u003C\u003Et__builder).get_Task();
}

No magic. The compiler just creates a state machine due to async/await keywords.

 [/*Attribute with token 0C000007*/CompilerGenerated]
  private sealed class \u003CMainAsync2\u003Ed__2 : IAsyncStateMachine
  {
    public int \u003C\u003E1__state;
    public AsyncTaskMethodBuilder \u003C\u003Et__builder;
    public UserQuery \u003C\u003E4__this;
    private TaskAwaiter \u003C\u003Eu__1;

    public \u003CMainAsync2\u003Ed__2()
    {
      base.\u002Ector();
    }

    void IAsyncStateMachine.MoveNext()
    {
      int num1 = this.\u003C\u003E1__state;
      try
      {
        TaskAwaiter taskAwaiter;
        int num2;
        if (num1 != 0)
        {
          taskAwaiter = Task.Delay(1000).GetAwaiter();
          // ISSUE: explicit reference operation
          if (!((TaskAwaiter) @taskAwaiter).get_IsCompleted())
          {
            this.\u003C\u003E1__state = num2 = 0;
            this.\u003C\u003Eu__1 = taskAwaiter;
            UserQuery.\u003CMainAsync2\u003Ed__2 mainAsync2D2 = this;
                ((AsyncTaskMethodBuilder) @this.\u003C\u003Et__builder).AwaitUnsafeOnCompleted<TaskAwaiter, UserQuery.\u003CMainAsync2\u003Ed__2>((M0&) @taskAwaiter, (M1&) @mainAsync2D2);
            return;
          }
        }
        else
        {
          taskAwaiter = this.\u003C\u003Eu__1;
          this.\u003C\u003Eu__1 = (TaskAwaiter) null;
          this.\u003C\u003E1__state = num2 = -1;
        }
        ((TaskAwaiter) @taskAwaiter).GetResult();
        taskAwaiter = (TaskAwaiter) null;
      }
      catch (Exception ex)
      {
        this.\u003C\u003E1__state = -2;
        ((AsyncTaskMethodBuilder) @this.\u003C\u003Et__builder).SetException(ex);
        return;
      }
      this.\u003C\u003E1__state = -2;
      ((AsyncTaskMethodBuilder) @this.\u003C\u003Et__builder).SetResult();
    }

    [/*Attribute with token 0C000008*/DebuggerHidden]
    void IAsyncStateMachine.SetStateMachine(/*Parameter with token 08000001*/IAsyncStateMachine stateMachine)
    {
    }
  }

The moral of this is simple: if you don't need to await, just return the Task. It will do the same, and you'll save a lot of unnecessary state machine construction, with its wasteful memory and execution where it's not needed.


Viewing all articles
Browse latest Browse all 96

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>