是否有任何理由在Task.Run中运行异步代码?

Kin*_*tic 5 c# async-await

我最近在WinForm应用程序中遇到了这段代码,但我无法确定是否有任何理由asyncTask.Run等待中的内部运行代码。

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);
}

Run Code Online (Sandbox Code Playgroud)

如果没有Task.Run,​​这段代码会不会做完全相同的事情?

public async Task SaveStuff()
{
    await SaveStuffAsync().ConfigureAwait(false);
    await SendToExternalApiAsync().ConfigureAwait(false);
}
Run Code Online (Sandbox Code Playgroud)

Jes*_*Wit 6

如果没有Task.Run,​​这段代码会不会做完全相同的事情?

如果async方法中的代码实际上是异步的,则不会有任何不同。该操作Task将在线程池上执行,因此可能需要更多资源才能执行。从调用线程的角度来看,您不会注意到差异。

但是,如果async方法中的代码是(不是)同步的,则您会发现有所不同。请考虑以下方法:

private async Task DoWorkNotReallyAsync()
{
    for (int i = 0; i < aVeryLargeNumber; i++)
    {
        DoSynchronousComputation();
    }
}
Run Code Online (Sandbox Code Playgroud)

上面的方法具有异步签名,但实际上将同步运行,因此在执行线程时将其阻塞。将调用包装在中Task.Run将把执行调度到线程池。因此,Task.Run如果要确保对async方法的调用不会阻塞当前线程,则将任务包装在其中可能会很有用。

您的示例中调用的方法看上去确实是异步的,因此我看不出将这些任务包装在中的理由Task.Run


Pau*_*ado 4

方法返回 a 的事实Task并不意味着它立即返回。例如,在 I/O 操作之前,它可能需要进行一些耗时/CPU 消耗的设置。

因此,在客户端 UI 上,通常会看到 UI 外部的所有内容都在内部调用Task.Run

话虽这么说,但情况并非如此:

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

这会导致 UI 线程中的一次额外执行仅用于调度线程池上的工作。

这会更容易被接受:

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

无需调用,ConfigureAwait(false)因为它保证没有SynchronizationContext.

此代码段与上一个代码段之间的区别在于调用该代码的位置和方式,