【问题标题】:Is there any reason to run async code inside a Task.Run? [duplicate]是否有任何理由在 Task.Run 中运行异步代码? [复制]
【发布时间】:2019-08-08 01:03:14
【问题描述】:

我最近在 WinForm 应用程序中遇到了这段代码,我不知道是否有任何理由在等待的 Task.Run 中运行 async 代码。

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

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

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

【问题讨论】:

  • 尽管SaveStuffAsync 这样的方法被声明为异步方法,但它总是有可能阻塞调用线程。在线程池线程上调用它可确保您的 UI 不会冻结,无论(可能)异步方法的实际实现如何。
  • @peter-duniho 那么到底哪个问题与这个问题重复?
  • 他们都讨论了通过Task.Run()调用非异步方法、通过Task.Run()调用异步方法和直接等待异步方法之间的区别。所以......所有这些。
  • @peter-duniho 我的问题根本不涉及非异步方法。

标签: c# async-await


【解决方案1】:

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

如果 async 方法中的代码实际上是异步的,那并不会真正产生影响。 Task 将在线程池上执行,因此可能需要更多资源来执行。从调用线程的角度来看,您不会注意到差异。

但是,如果您的异步方法中的代码是(不是)同步的,您会注意到不同之处。考虑以下方法:

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

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

您的示例中调用的方法看起来确实是异步的,所以我认为没有理由将这些任务包装在 Task.Run 中。

【讨论】:

  • 即使该方法实际上是异步的,它仍然可能有很长的 cpu 计算会阻塞
  • @johnny5 我想在这种情况下我会称它为实际上不是异步的
  • 如果您通过网络进行调用或 io 操作,那么您就是异步的,如果您对该数据进行计算,我仍然会说您实际上是异步的。唯一不是真正异步的情况是你提供的情况,只做计算工作
  • 例如,默认情况下 EF Core 的 SaveChangesAsync(来自示例)会在保存之前进行更改检测。这将阻止调用者。
【解决方案2】:

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

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

话虽如此,事实并非如此:

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

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

这样会更容易接受:

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

没有必要调用ConfigureAwait(false),因为它保证没有SynchronizationContext

这个和你上一个 sn-p 之间的区别在于调用该代码的位置和方式,

【讨论】:

    猜你喜欢
    • 2019-02-26
    • 2013-07-16
    • 1970-01-01
    • 2018-11-16
    • 1970-01-01
    • 2016-12-28
    • 2013-01-07
    • 2023-01-13
    • 1970-01-01
    相关资源
    最近更新 更多