同步调用第三方异步任务方法

本文关键字:任务 方法 异步 第三方 调用 同步 | 更新日期: 2023-09-27 18:36:55

我正在阅读数百个关于await/async的答案,但这个简单的问题仍然没有答案。有一个等待的方法,由其他人编写,有必要等到它完成。

// cannot be changed
public async Task ThirdPartyAsync(...)
{
    // calls other awaitable methods
    // takes few seconds
}
// I'd like something like this
public void MyPatientCallingMethod(...)
{
    // 1. call the method
    ThirdPartyAsync(...);
    // 2. wait for ThirdPartyAsync to finish (I don't care how long) 
    // 3. return after
    return;
}

是的,我知道,它会阻塞主线程、UI 线程、任何其他线程......出于安全原因,必须以这种方式解决。

问题是async方法的返回值是Task,并且没有.Result标签可以等待。
还有其他无死锁的解决方案吗?

同步调用第三方异步任务方法

您可以使用

Task.Wait()来阻止任务。

// call the method and wait for ThirdPartyAsync to finish (I don't care how long) 
ThirdPartyAsync(...).Wait();
那是

因为最好的答案是"不要"。你真的应该努力做到一直异步。

我不能使用 await,因为我必须确保该方法在执行其他任何操作之前完成。

请注意,async是串行的(这意味着该方法在内部方法完成之前不会继续超过await);它只是异步串行而不是同步串行。因此,相同的编程结构和逻辑同样适用:

public async Task MyPatientCallingMethodAsync(...)
{
  // 1. call the method
  var task = ThirdPartyAsync(...);
  // 2. wait for ThirdPartyAsync to finish
  await task;
  // 3. return after
  return;
}

这正是您应该在绝大多数情况下采取的方法。

但是,如果您肯定地,绝对不能这样做,那么您的选择是有限的。斯蒂芬·图布在他的博客上描述了你的选择。

重要的是要注意,没有一个选项适用于所有情况,并且每个选项都有缺点和陷阱。因此,我们谁也不能说哪一个是最好的,甚至哪个可以工作,因为我们不知道您的代码在什么上下文中执行。

总之,您的选择是:

  • 使用 GetAwaiter().GetResult()Wait 同步阻止(有关差异,请参阅 @l3arnon 的答案)。 例如 ThirdPartyAsync(...).GetAwaiter().GetResult(); .此方法的缺点是,如果在 UI 或 ASP.NET 上下文中调用,则确实有可能出现死锁。
  • 卸载到线程池线程并阻止该线程。 例如,Task.Run(() => ThirdPartyAsync(...)).GetAwaiter().GetResult(); .此方法的缺点是第三方代码必须在备用线程上运行,因此这不适用于假定 UI 或 ASP.NET 上下文的代码。
  • 执行嵌套消息循环。例如,AsyncContext.Run(() => ThirdPartyAsync(...)); .此方法的缺点是嵌套循环可能无法执行您需要的所有操作(即,它可能不会泵送第三方代码可能需要的 Win32 消息),嵌套上下文与预期的上下文不匹配(即,这可能导致 ASP.NET 调用失败),并且嵌套循环可能执行太多操作(即,它可能会泵送 Win32 消息, 这可能会导致意外的重入)。

简而言之,虽然听起来很简单,但"异步同步"是您可以(尝试)做的最复杂的事情之一。这就是"一直使用异步!"这个常见答案背后的长篇解释:)

任何没有泛型参数的Task都缺少Result,因为它实际上是一个void返回 - 没有Result等待。

相反,您正在寻找的是Task.Wait()(如其他答案中所述)。

从评论中可以明显看出误解是什么:您假设await启动一个新线程并继续执行。情况正好相反:await暂停执行,直到其参数完成。您调用的 Task 返回方法应该启动异步。 await消耗了它。

使用await .现在,这是在不阻止 UI 的情况下执行长时间运行的工作的标准方法。

您可以使用:

  1. Wait() - 如果任务出错,将抛出AggregateException
  2. GetAwaiter().GetResult() - 将抛出第一个异常(就像await一样)。
  3. 斯蒂芬·克利利AsyncContext

public void MyPatientCallingMethod(...)
{
    AsyncContext.Run(() => ThirdPartyAsync(...));
}

AsyncContext 类型提供用于执行异步操作的上下文。await 关键字需要上下文才能返回;对于大多数程序,这是一个 UI 上下文,您不必担心它。ASP.NET 还提供了适当的上下文,您不必使用自己的上下文。
但是,控制台应用程序和 Win32 服务没有合适的上下文,AsyncContext可以在这些情况下使用。