Search code examples
c#.netmultithreadingasync-await

Difference between calling an async method, and wrapping the call in Task.Run


I am trying to write an async job which runs in the background. There are two ways of doing it-

[Test]
public async Task method1()
{ 
    Task task = run();
    await Task.WhenAll(task);
}

[Test]
public async Task method2()
{
    Task task = Task.Run(async() =>
    {
        await run();
    });
    await Task.WhenAll(task);
}

public async Task run()
{
    Console.WriteLine("Current Thread Name: " + Thread.CurrentThread.Name);
    // background thread to print numbers with sleep of 1s
    await Task.Run(() =>
    {
        for (int i = 1; i <= 2; i++)
        {
            Console.Write(i + " ");
            Thread.Sleep(1000);
        }
        Console.WriteLine();
    });
}

method1 prints

Current Thread Name: NonParallelWorker
1 2

where as method2 prints

Current Thread Name: .NET TP Worker
1 2

I was wondering what is the difference between these 2 approaches and how it will impact my application. Functionally, both kind of accomplish the same thing - running a job in background asynchronously.


Solution

  • The Task.Run method invokes an action on the ThreadPool, and returns a Task that represents the completion of this action. In case the action is asynchronous, as in your case, the Task.Run still invokes the action on the ThreadPool, and returns a proxy Task that represents both the creation of the inner task and then the completion of this task. If you are interested in more details, you can read this Microsoft article.

    If you want to offload a piece of synchronous code to the ThreadPool, wrapping it once in Task.Run is enough. Wrapping it twice or more, adds nothing but friction:

    // Exercise to futility
    Task task = Task.Run(async () =>
    {
        await Task.Run(async () =>
        {
            await Task.Run(async () =>
            {
                await Task.Run(async () =>
                {
                    await Task.Run(async () =>
                    {
                        await run();
                    });
                });
            });
        });
    });
    

    I was wondering what is the difference between these 2 approaches and how it will impact my application.

    Wrapping two Task.Runs the one inside the other will make no observable difference. The impact in performance is unlikely to be noticeable. .NET's ThreadPool is a very efficient piece of software, and can handle such misuse with ease.