如何正确阻止异步代码?

Ale*_*kiy 7 .net c# async-await configureawait

我有以下方式编写的大量代码:

public string SomeSyncOperation(int someArg)
{
   // sync code 
   SomeAsyncOperation(someArg, someOtherArg).ConfigureAwait(false).GetAwaiter().GetResult()
   // sync code
};
Run Code Online (Sandbox Code Playgroud)

这里我们有一些必须访问异步api的同步代码,因此它会阻塞,直到结果准备就绪.我们不能改变签名方法并在async这里添加.所以,我们无论如何都在等待,所以我们需要ConfigureAwait(false)在这里吗?我很确定我们没有,但我有点害怕删除它,因为它可能涵盖了一些用例(或者为什么我几乎到处都看到它?这只是一个货物崇拜?)并且删除此调用可能导致一些不安全的结果.

那么它有意义吗?

Eri*_*ert 15

如何正确阻止异步代码?

您没有正确阻止异步代码.阻止是错误的.询问做错事的正确方法是非首发.

由于以下情况,阻止异步代码是错误的:

  • 我有一个表示异步操作的对象.
  • 异步操作本身异步等待第二次异步操作的完成.
  • 当消息循环执行与此线程的消息队列中存在的消息相关联的代码时,将对该线程调度第二个异步操作.

现在,当您尝试同步获取第一个异步操作的结果时,您可以找出可怕的错误.它会阻塞,直到它的子异步操作完成,这将永远不会发生,因为现在我们已经阻止了将来为该请求提供服务的线程!

你的选择是:

  1. 使整个调用堆栈正确异步和await结果.
  2. 不要使用此API.从头开始编写一个您知道不会死锁的等效同步API,并正确调用它.
  3. 写一个不正确的程序,有时会无法预测死锁.

有两种方法可以编写正确的程序; 在异步函数上编写同步包装是危险和错误的.

现在,您可能会问,是不是ConfigureAwait通过删除我们在当前上下文中恢复的要求来解决问题? 这不是我们担心的恢复点.如果您将依赖于ConfigureAwait将您从死锁中解救出来,那么堆栈中的每个异步操作都必须使用它,而我们不知道即将导致死锁的底层异步操作是否会这样做!

如果上述内容对您来说并不完全清楚,请阅读斯蒂芬关于为什么这是一个不好的做法的文章,以及为什么常见的解决方法只是危险的黑客攻击.

https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html

他的最新文章提供了更多的黑客和解决方法:

https://msdn.microsoft.com/en-us/magazine/mt238404.aspx?f=255&MSPPError=-2147217396

但同样重要的是:正确的做法是重新设计你的程序,以便拥抱异步并await在整个过程中使用.不要试图解决它.

因为这个方法有~20个方法的堆栈跟踪,其中一些是实现一些接口.将其更改为异步需要在~50个文件中进行更改声明,并将完全同步接口转换为混合接口.

然后忙!听起来很简单.

  • 更多可用的黑客[这里](https://msdn.microsoft.com/en-us/magazine/mt238404.aspx).我总是给这个问题的回答是:"有各种各样的黑客,但每个都有缺点,并且没有一个在所有场景中都有效". (2认同)
  • 但如果糟糕场景中的第三颗子弹从未发生怎么办?如果您在手动创建的线程中阻塞任务会怎样?“封锁是错误的”听起来有些极端。 (2认同)