Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the overhead of an "synchronized" async method?

Tags:

c#

async-await

Here is a benchmark for methods return task, but run synchronizely under the hood.

class MainClass
{
    public static async Task<int> UsingAsyncModifier()
    {
        return 10;
    }

    public static Task<int> UsingTaskCompletionSource()
    {
        TaskCompletionSource<int> tcs = new TaskCompletionSource<int>();
        tcs.SetResult(10);
        return tcs.Task;
    }

    public static Task<int> UsingTaskFromResult()
    {
        return Task.FromResult(10);
    }

    public static void Main(string[] args)
    {
        DateTime t = DateTime.Now;
        const int repeat = 10000; // Results volatile while repeat grows.
        Console.WriteLine("Repeat {0} times.", repeat);

        int j = 0;
        for (int i = 0; i < repeat; i++)
        {
            j += UsingAsyncModifier().Result;
        }
        Console.WriteLine("UsingAsyncModifier: {0}", DateTime.Now - t);
        t = DateTime.Now;

        for (int i = 0; i < repeat; i++)
        {
            j += UsingTaskCompletionSource().Result;
        }
        Console.WriteLine("UsingTaskCompletionSource: {0}", DateTime.Now - t);
        t = DateTime.Now;

        for (int i = 0; i < repeat; i++)
        {
            j += UsingTaskFromResult().Result;
        }
        Console.WriteLine("UsingTaskFromResult: {0}", DateTime.Now - t);
    }
}

Output (repeat 10,000/100,000/1000,000 times):

Repeat 10000 times.
UsingAsyncModifier: 00:00:00.1043980
UsingTaskCompletionSource: 00:00:00.0095270
UsingTaskFromResult: 00:00:00.0089460

Repeat 10,000 times, UsingTaskFromResult 10x faster than UsingAsyncModifier.

Repeat 100000 times.
UsingAsyncModifier: 00:00:00.1676000
UsingTaskCompletionSource: 00:00:00.0872020
UsingTaskFromResult: 00:00:00.0870180

Repeat 100,000 times, UsingTaskFromResult 2x faster than UsingAsyncModifier.

Repeat 1000000 times.
UsingAsyncModifier: 00:00:00.8458490
UsingTaskCompletionSource: 00:00:00.8870980
UsingTaskFromResult: 00:00:00.9027320

Repeat 1,000,000 times, UsingAsyncModifier slightly faster than UsingTaskFromResult.

What I think was, the async modifier just created an completed Task, something like Task.FromResult() does. But the benchmark does not prove my idea. Why?

like image 345
Logan Avatar asked Nov 02 '22 21:11

Logan


1 Answers

While I see the similar results using DateTime, the use of Stopwatch for time measuring shows that iterations using UsingAsyncModifier() take 2 times more time duration (than using UsingTaskCompletionSource() or UsingTaskFromResult(), both showing equal appr. duration) even with 1 000 000 iterations

Here is output:

Repeat 1000000 times.
UsingAsyncModifier: 5458
UsingTaskCompletionSource: 2838
UsingTaskFromResult: 2556

with your code using Stopwatch

class Program
{
     public static async Task<int> UsingAsyncModifier()
    {
        return 10;
    }

    public static Task<int> UsingTaskCompletionSource()
    {
        TaskCompletionSource<int> tcs = new TaskCompletionSource<int>();
        tcs.SetResult(10);
        return tcs.Task;
    }
    public static Task<int> UsingTaskFromResult()
    {
        return TaskEx.FromResult(10);
    }
    static void Main(string[] args)
    {
      //DateTime t = DateTime.Now;
      Stopwatch timer = new Stopwatch();
      const int repeat = 1000*1000; // Results volatile while repeat grows.
      Console.WriteLine("Repeat {0} times.", repeat);

        int j = 0;
        //DateTime t = DateTime.Now;
        timer.Start();
        for (int i = 0; i < repeat; i++)
        {
            j += UsingAsyncModifier().Result;
        }
        timer.Stop();
        Console.WriteLine("UsingAsyncModifier: {0}"
                          , timer.ElapsedMilliseconds);
        //t = DateTime.Now;
        timer.Reset();

        j = 0;

        timer.Start();
        for (int i = 0; i < repeat; i++)
        {
            j += UsingTaskCompletionSource().Result;
        }
        timer.Stop();
        Console.WriteLine("UsingTaskCompletionSource: {0}"
                           , timer.ElapsedMilliseconds);
        //t = DateTime.Now;
        timer.Reset();
        j = 0;
        timer.Start();
        for (int i = 0; i < repeat; i++)
        {
          j += UsingTaskFromResult().Result;
        }
        timer.Stop();
        Console.WriteLine("UsingTaskFromResult: {0}"
                          , timer.ElapsedMilliseconds);

        Console.ReadLine();
    }
}

Stephen Toub in his "Async Performance: Understanding the Costs of Async and Await" explains:

When working with synchronous code, methods with empty bodies are practically free. This is not the case for asynchronous methods

Read it for much more details

like image 159