![](/img/trans.png)
[英]In the context of ASP.NET, why doesn't Task.Run(…).Result deadlock when calling an async method?
[英]Why does this wrapping in Task.Run remove deadlock from asp.net core?
我正在尝试修复一种方法,该方法应该使用内置的GetOrCreateAsync
从MemoryCache
检索任意数据。 这是 Microsoft 的示例(来自https://learn.microsoft.com/en-us/aspnet/core/performance/caching/memory?view=aspnetcore-3.0 ):
public async Task<IActionResult> CacheGetOrCreateAsynchronous()
{
var cacheEntry = await
_cache.GetOrCreateAsync(CacheKeys.Entry, entry =>
{
entry.SlidingExpiration = TimeSpan.FromSeconds(3);
return Task.FromResult(DateTime.Now);
});
return View("Cache", cacheEntry);
}
这是我的:
/// <summary>
/// This deadlocks :(
/// </summary>
/// <param name="dayKey"></param>
/// <returns></returns>
public async Task<IEnumerable<Document>> GetEventsForDay(string dayKey)
{
CheckCacheKeyExists(dayKey);
return await _cache.GetOrCreateAsync(dayKey, async entry =>
{
entry.SetOptions(_cacheEntryOptions);
var events = await EventsForDay(dayKey).ConfigureAwait(false);
return events;
}).ConfigureAwait(false);
}
EventsForDay
的签名为
private async Task<IEnumerable<Document>> EventsForDay(string dayKey)
EventsForDay
中的所有异步操作都是await
和ConfigureAwait(false)
以尝试防止死锁。 然而,即使使用那些ConfigureAwait(false)
,上面的异步 lambda 仍然会死锁并且永远不会返回。
但是,如果我使用此处详述的解决方案: https ://stackoverflow.com/a/40569410 它不再死锁:
/// <summary>
/// This does not deadlock. Credit here: https://stackoverflow.com/a/40569410
/// </summary>
/// <param name="dayKey"></param>
/// <returns></returns>
public async Task<IEnumerable<Document>> GetEventsForDay(string dayKey)
{
CheckCacheKeyExists(dayKey);
var cachedItem = await Task.Run(async () =>
{
return await _cache.GetOrCreateAsync(dayKey, async entry =>
{
entry.SetOptions(_cacheEntryOptions);
var events = await EventsForDay(dayKey).ConfigureAwait(false);
return events;
}).ConfigureAwait(false);
}).ConfigureAwait(false);
return cachedItem;
}
我的问题是:
1.) 为什么将我的异步 lambda 包装在Task.Run
中可以消除死锁?
2.) 如果GetEventsForDay
之后的整个调用堆栈除了使用自己的ConfigureAwait(false)
await
lambda 本身之外,任何await
ed 方法总是使用ConfigureAwait(false)
,为什么我的异步 lambda 会首先死锁?
好问题。
当你执行Task.Run
时,你传递给它的委托将在线程池上运行,但重要的是在这里对你来说,而不是使用SynchronizationContext
。 当您阻塞某些代码的结果时,这通常会给您带来死锁,而这些代码本身具有对该上下文的独占访问权限。
所以Task.Run
“修复它”,除了它不是真的,它只是掩盖了另一个问题,你在异步工作中阻塞了线程。 这最终会导致线程池在加载时饥饿,其中 .NET 无法添加更多线程来取消阻止当前工作。
使用await
时的.ConfigureAwait(false)
意味着如果Task
未完成,则以下代码不需要在当前SynchronizationContext
上运行,这也会导致前面提到的死锁情况。
人们经常有的一个疏忽是,当Task
已经完成时,它实际上什么都不做,例如Task.FromResult
,或者您可能有一些缓存结果。 在这种情况下, SynchronizationContext
会进一步流入您的代码,从而增加一些后续代码阻塞仍然存在的异步Task
的机会,从而导致死锁。
回到您的示例,您在代码中放入了多少.ConfigureAwait(false)
并不重要,如果在await
之前运行的代码对异步代码进行了一些阻塞,那么您仍然容易陷入死锁。 假设死锁的来源发生在EventsForDay
中(不要这样做),这也会为您修复它:
public async Task<IEnumerable<Document>> GetEventsForDay(string dayKey)
{
CheckCacheKeyExists(dayKey);
var cachedItem = await _cache.GetOrCreateAsync(dayKey, async entry =>
{
await Task.Delay(100).ConfigureAwait(false); // this would also stop the deadlock
entry.SetOptions(_cacheEntryOptions);
var events = await EventsForDay(dayKey);
return events;
});
return cachedItem;
}
这也是:
public async Task<IEnumerable<Document>> GetEventsForDay(string dayKey)
{
CheckCacheKeyExists(dayKey);
var cachedItem = await _cache.GetOrCreateAsync(dayKey, async entry =>
{
SynchronizationContext.SetSynchronizationContext(null); // this would stop it too
entry.SetOptions(_cacheEntryOptions);
var events = await EventsForDay(dayKey);
return events;
});
return cachedItem;
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.