问题 使用Task.Run调用异步方法似乎错了?


我最近遇到了这个由我们为我们工作的承包商编写的代码。它要么是非常聪明或愚蠢(我认为后者,但我想要第二个意见)。我并没有大规模地加快速度 async  await

基本上它的工作方式如下:

public bool Send(TemplatedMessageDto message)
{
    return Task.Run(() => SendAsync(message))
        .GetAwaiter()
        .GetResult();
}

public async Task<bool> SendAsync(TemplatedMessageDto message)
{
    //code doing stuff
    var results = await _externalresource.DothingsExternally();
    //code doing stuff
}

现在,我首先理解它 Task.Run() 没有意义,也没有效率?应该是:

public bool Send(TemplatedMessageDto message)
{
    return SendAsync(message))
    .GetAwaiter()
    .GetResult();
}

public async Task<bool> SendAsync(TemplatedMessageDto message)
{
    //code doing stuff
    var results = await _externalresource.DothingsExternally();
    //code doing stuff
}

我也不相信这真的是一种异步方法,因为它仍然会等待,对吧?我认为唯一的优势(甚至是重写)就是释放主要的工作者线程。

有人可以确认第一个任务不应该存在吗?


9335
2017-09-16 10:48


起源

为什么 GetAwaiter().GetResult() 打电话而不是 .Result?为什么呢 等待 根本不是写作 public async Task<bool> Send ... await Task.Run(); ? - Panagiotis Kanavos
如果“其他东西”足够重 Task.Run 效率不高 - async 不 使 任何异步运行的东西。一切都达到了 await 将在调用线程上运行。如果你需要它同时运行,你应该使用 Task.Run或者更好的是,将它提取到自己的方法,该方法将使用Task.Run调用 - Panagiotis Kanavos
@PanagiotisKanavos,嗯,是的,这确实让我想到了。我不认为他真的明白他在做什么。 - Liam
你真的吗? 需要 同步版? - Gusdor


答案:


我也不相信这真的是一种异步方法,因为它仍然会等待,对吧?

正如尤瓦尔解释的那样,它不是。您不应该使用同步异步。

现在,我首先理解它 Task.Run() 没有意义,也没有效率?

不是真的,使用有价值 Task.Run 以这样的方式。

由于您在异步方法上阻塞(您不应该这样做),因此您可能会陷入僵局。这种情况发生在UI应用程序和asp.net中 SynchronizationContext

运用 Task.Run 清除那个 SynchronizationContext 因为它将工作卸载到了 ThreadPool 线程并消除死锁的风险。

因此,阻塞是不好的,但如果你最终使用它 Task.Run 更安全。


8
2017-09-16 11:16



你能澄清一下吗? 阻止异步方法(你不应该这样做) 位。我现在有点困惑。该 _externalresource 是一个返回的第三方库 Task<List<value>> 和这个方法(我的 Send() 方法)只需要确认该列表包含值。我只是将其改为syncohonous方法并使用 .Result,但我认为你的说法是个坏主意? - Liam
@Liam阻塞异步方法通常是一个坏主意。它会伤害性能并导致死锁。最好让整个流程异步。 - i3arnon
@Liam如果你必须有一个同步选项,那么如果你一直使用同步调用会更好(即 var results = _externalresource.DothingsExternally())但如果​​这不存在那么同步异步是不可避免的。 - i3arnon
也许这是图书馆那么傻。我想我可以发一个问题。谢谢你的帮助。 - Liam
我有 发布了一个跟进问题 如果您感兴趣? - Liam


我也不相信这真的是一种异步方法,因为它仍然会等待,对吧?

你的承包商做的是使用 通过异步反模式同步。他可能这样做是为了避免创建一个同步完成其工作的附加方法。他不必要地援引 Task.Run 并立即阻止它使用 GetResult

运用 GetAwaiter().GetResult() 将传播内部异常,如果发生这种情况,而不是包装 AggregateException

我认为唯一的优势(甚至是重写)就是释放主要的工作者线程。

您的版本和他的将在执行时阻止主线程,而他的也将通过使用线程池线程来执行此操作。正如Bar所提到的,这可以帮助避免与同步上下文编组有关的问题的死锁。如果需要,我建议创建一个同步等效项。


7
2017-09-16 11:00