如何正确阻止异步代码?
How to correctly block on async code?
我有大量代码是按以下方式编写的:
public string SomeSyncOperation(int someArg)
{
// sync code
SomeAsyncOperation(someArg, someOtherArg).ConfigureAwait(false).GetAwaiter().GetResult()
// sync code
};
这里我们有一些同步代码必须访问异步 api,所以它会阻塞直到结果准备好。我们无法更改签名并在此处添加 async
。所以,反正我们是同步等待的,所以这里需要ConfigureAwait(false)
吗?我很确定我们没有,但我有点害怕删除它,因为它可能涵盖了一些用例(或者为什么我几乎到处都能看到它?这只是一个货物崇拜?)并且删除这个电话可能导致一些不安全的结果。
那么它有意义吗?
How to correctly block on async code?
您没有正确阻塞异步代码。阻塞是错误的。问什么是做错事的正确方法是行不通的。
由于以下情况,阻塞异步代码是错误的:
- 我手头有一个代表异步操作的对象。
- 异步操作本身正在异步等待第二个异步操作的完成。
- 当消息循环执行与当前线程的消息队列中的消息关联的代码时,第二个异步操作将安排到该线程。
现在您可以弄清楚当您尝试同步获取第一个异步操作的结果时出现了什么可怕的错误。它会阻塞直到它的子异步操作完成,这永远不会发生,因为现在我们已经阻塞了将来要为请求提供服务的线程!
您的选择是:
- 使您的整个调用堆栈正确异步并
await
结果。
- 不要使用这个 API。从头开始写一个你知道不会死锁的等效同步API,并正确调用它。
- 编写了一个有时会意外死锁的错误程序。
编写正确的程序有两种方法;在异步函数上编写同步包装器是危险且错误的。
现在,您可能会问,ConfigureAwait
不是通过删除我们在当前上下文中恢复的要求来解决问题吗? 这不是我们担心的恢复点。如果你要依赖ConfigureAwait
来避免死锁那么每个栈中的异步操作都必须使用它,我们不知道底层异步是否即将导致死锁的操作就是这样做的!
如果以上内容对您来说不是很清楚,请阅读 Stephen 的文章,了解为什么这是一种不好的做法,以及为什么常见的解决方法只是危险的黑客攻击。
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
。不要试图解决它。
becuase this method has stacktrace of ~20 methods, some of them are implementing some interfaces. Changing it to be async require change declarations in ~50 files, and we convert fully sync interfaces to mixed ones.
那就忙吧!这听起来很简单。
我有大量代码是按以下方式编写的:
public string SomeSyncOperation(int someArg)
{
// sync code
SomeAsyncOperation(someArg, someOtherArg).ConfigureAwait(false).GetAwaiter().GetResult()
// sync code
};
这里我们有一些同步代码必须访问异步 api,所以它会阻塞直到结果准备好。我们无法更改签名并在此处添加 async
。所以,反正我们是同步等待的,所以这里需要ConfigureAwait(false)
吗?我很确定我们没有,但我有点害怕删除它,因为它可能涵盖了一些用例(或者为什么我几乎到处都能看到它?这只是一个货物崇拜?)并且删除这个电话可能导致一些不安全的结果。
那么它有意义吗?
How to correctly block on async code?
您没有正确阻塞异步代码。阻塞是错误的。问什么是做错事的正确方法是行不通的。
由于以下情况,阻塞异步代码是错误的:
- 我手头有一个代表异步操作的对象。
- 异步操作本身正在异步等待第二个异步操作的完成。
- 当消息循环执行与当前线程的消息队列中的消息关联的代码时,第二个异步操作将安排到该线程。
现在您可以弄清楚当您尝试同步获取第一个异步操作的结果时出现了什么可怕的错误。它会阻塞直到它的子异步操作完成,这永远不会发生,因为现在我们已经阻塞了将来要为请求提供服务的线程!
您的选择是:
- 使您的整个调用堆栈正确异步并
await
结果。 - 不要使用这个 API。从头开始写一个你知道不会死锁的等效同步API,并正确调用它。
- 编写了一个有时会意外死锁的错误程序。
编写正确的程序有两种方法;在异步函数上编写同步包装器是危险且错误的。
现在,您可能会问,ConfigureAwait
不是通过删除我们在当前上下文中恢复的要求来解决问题吗? 这不是我们担心的恢复点。如果你要依赖ConfigureAwait
来避免死锁那么每个栈中的异步操作都必须使用它,我们不知道底层异步是否即将导致死锁的操作就是这样做的!
如果以上内容对您来说不是很清楚,请阅读 Stephen 的文章,了解为什么这是一种不好的做法,以及为什么常见的解决方法只是危险的黑客攻击。
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
。不要试图解决它。
becuase this method has stacktrace of ~20 methods, some of them are implementing some interfaces. Changing it to be async require change declarations in ~50 files, and we convert fully sync interfaces to mixed ones.
那就忙吧!这听起来很简单。