Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there any reason to run async code inside a Task.Run? [duplicate]

Tags:

c#

async-await

I've recently come across this code in a WinForm application and I can't figure if there is any reason to run async code inside a Task.Run that is awaited.

public async Task SaveStuff()
{
    await Task.Run(() => SaveStuffAsync().ConfigureAwait(false));
    await Task.Run(() => SendToExternalApiAsync().ConfigureAwait(false));
}

private async Task SaveStuffAsync()
{
    await DbContext.SaveChangesAsync().ConfigureAwait(false);
}

private async Task SendToExternalApiAsync()
{
    // some async code that is awaited with ConfigureAwait(false);
}

Wouldn't this code do the exact same thing without the Task.Run?

public async Task SaveStuff()
{
    await SaveStuffAsync().ConfigureAwait(false);
    await SendToExternalApiAsync().ConfigureAwait(false);
}
like image 929
Kinetic Avatar asked Aug 07 '19 13:08

Kinetic


2 Answers

Wouldn't this code do the exact same thing without the Task.Run?

If the code inside the async method is actually asynchronous, it will not really make a difference. The Task would be executed on the threadpool, so it may take some more resources to execute. From the calling thread point of view, you would not notice the difference.

If, however, the code inside your async method is (not a)synchronous, you will notice a difference. Consider the following method:

private async Task DoWorkNotReallyAsync()
{
    for (int i = 0; i < aVeryLargeNumber; i++)
    {
        DoSynchronousComputation();
    }
}

The above method has an async signature, but will actually be run synchronously, thus blocking the calling thread while it executes. Wrapping the call in a Task.Run will schedule the execution to the threadpool. Therefore wrapping tasks in Task.Run might be useful if you want to be certain that a call to the async method will not block the current thread.

The methods called in your example do look truly asynchronous, so I wouldn't see a reason to wrap those tasks in Task.Run.

like image 80
Jesse de Wit Avatar answered Oct 21 '22 21:10

Jesse de Wit


The fact that a method returns a Taskdoesn't mean it yields back immediately. It might have have some time/CPU consuming setup before an I/O operation, for example.

For that reason, it is usual to see, on client UIs, everything outside of the UI being called inside Task.Run.

That being said, this is not such a case:

public async Task SaveStuff()
{
    await Task.Run(() => SaveStuffAsync().ConfigureAwait(false));
    await Task.Run(() => SendToExternalApiAsync().ConfigureAwait(false));
}

That causes one extra execution in the UI thread only to schedule work on the thread pool.

this would be more acceptable:

public async Task SaveStuff()
{
    await Task.Run(
        async () =>
        {
            await SaveStuffAsync();
            await SendToExternalApiAsync();
        });
}

There's no need to invoke ConfigureAwait(false) because it's guaranteed to not have a SynchronizationContext.

The difference between this and your last snippet is where and how that code is being invoked,

like image 36
Paulo Morgado Avatar answered Oct 21 '22 22:10

Paulo Morgado