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

时间:2015-01-09 21:18:11

标签: c# async-await

我正在阅读关于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标记可供使用。
还有其他无死锁的解决方案吗?

5 个答案:

答案 0 :(得分:4)

您可以使用Task.Wait()来阻止任务。

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

答案 1 :(得分:3)

那是因为最好的答案是“不要”。你应该一直努力保持异步。

  

我不能使用等待因为我必须确保方法在我做任何其他事情之前完成。

请注意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;
}

这正是你应该采取的大部分时间浩大的方法。

但是,如果你肯定,不能这样做,那么你的选择是有限的。 Stephen Toub describes your options on his blog

重要的是要注意所有方案都不适用每个选项都有缺点和缺陷。所以我们都不能说哪一个是最好的,甚至哪一个都工作,因为我们不知道你的代码在什么上下文中执行。

总之,您的选择是:

  • 使用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消息,这可能导致意外的重入)。

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

答案 2 :(得分:2)

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

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

答案 3 :(得分:2)

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

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

答案 4 :(得分:1)

您可以使用:

  1. Wait() - 如果任务出现故障,则会抛出AggregateException
  2. GetAwaiter().GetResult() - 会抛出第一个异常(如await那样)。
  3. Stephen Cleary's AsyncContext
  4. public void MyPatientCallingMethod(...)
    {
        AsyncContext.Run(() => ThirdPartyAsync(...));
    }
    
      

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