在Linq表达式中使用".Result"调用异步方法会导致死锁



我们遇到了一个错误,必须使用异步方法验证对象列表。代码的编写者想把它塞进一个Linq表达式中,就像这样:

var invalidObjects = list
.Where(x => _service.IsValidAsync(x).Result)
.ToList();

验证方法看起来像这样:

public async Task<bool> IsValidAsync(object @object) {
var validObjects = await _cache.GetAsync<List<object>>("ValidObjectsCacheKey");
return validObjects.Contains(@object);
}

这个小的解决方案导致整个应用程序挂在await _cache.GetAsync线上。缓存是一个分布式缓存(redis(。在将linq更改为简单的foreach并正确等待_service.IsValidAsync之后,代码基本上在瞬间无死锁地运行。

我基本上理解async-await是如何工作的,但我无法理解为什么会发生这种情况,尤其是因为列表只有一个对象。

欢迎任何建议!

编辑:该应用程序运行在.net Core 2.2上,但发生问题的库的目标是.netstandard 2.0。System.Threading.SynchronizationContext.Current在死锁时返回null

EDIT2:它改变了缓存提供程序(但仍然通过异步方法访问它(也解决了这个问题,所以错误实际上可能在redis缓存客户端中:https://github.com/aspnet/Caching/blob/master/src/Microsoft.Extensions.Caching.StackExchangeRedis/RedisCache.cs

除了已经提到的混合异步等待和阻塞调用(如.Result.Wait()(的问题之外

参考Async/Await-异步编程的最佳实践

为了总结第二条准则,您应该避免混合异步和阻塞代码。混合异步和阻塞代码可能会导致死锁、更复杂的错误处理和上下文线程的意外阻塞。本指南的例外是控制台应用程序的Main方法,或者——如果您是高级用户——管理部分异步代码库。

正如您已经发现的,有时简单的方法是遍历列表并适当地等待异步函数

例如

var invalidObjects = //...
foreach(var x in list){
if(!(await _service.IsValidAsync(x)))
invalidObjects.Add(x);
}

最新更新