C# 为什么在Task.Run中进行包装会从asp.net core中移除死锁?
我正在尝试修复一个方法,该方法应该是使用内置的C# 为什么在Task.Run中进行包装会从asp.net core中移除死锁?,c#,asynchronous,asp.net-core,C#,Asynchronous,Asp.net Core,我正在尝试修复一个方法,该方法应该是使用内置的GetOrCreateAsync从MemoryCache检索任意数据。以下是Microsofts的示例(来自): 并且EventsForDay中的所有异步操作都是await和ConfigureAwait(false),以尝试防止死锁。然而,即使使用那些ConfigureAwait(false),上面的异步lambda仍然会死锁并且永远不会返回 但是,如果我使用此处详述的解决方案:它不再死锁: /// <summary> /// This
GetOrCreateAsync
从MemoryCache
检索任意数据。以下是Microsofts的示例(来自):
并且EventsForDay
中的所有异步操作都是await
和ConfigureAwait(false)
,以尝试防止死锁。然而,即使使用那些ConfigureAwait(false)
,上面的异步lambda仍然会死锁并且永远不会返回
但是,如果我使用此处详述的解决方案:它不再死锁:
/// <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;
}
//
///这并不是死锁。这里的信用:https://stackoverflow.com/a/40569410
///
///
///
公共异步任务GetEventsForDay(字符串dayKey)
{
CheckCacheKeyExists(dayKey);
var cachedItem=wait Task.Run(异步()=>
{
return await\u cache.GetOrCreateAsync(dayKey,async entry=>
{
entry.SetOptions(_cacheEntryOptions);
var events=await events forday(dayKey).ConfigureAwait(false);
返回事件;
}).配置等待(错误);
}).配置等待(错误);
返回cachedItem;
}
我的问题是:
1.)为什么将异步lambda包装到任务中。运行会消除死锁
2.)如果GetEventsForDay
之后的整个调用堆栈总是使用ConfigureAwait(false)
作为任何await
ed方法,那么为什么我的异步lambda首先会死锁?除了lambda本身被wait
用自己的ConfigureAwait(false)
等待之外,还有一个很好的问题
执行Task.Run
时,传递给它的委托将在线程池中运行,但这里对您来说很重要,而不是使用SynchronizationContext
。当您阻塞某些代码的结果时,这通常会导致死锁,而这些代码本身具有对该上下文的独占访问权
所以Task.Run
“修复它”,除了它实际上没有,它只是掩盖了另一个问题,您正在阻止异步工作中的线程。这最终会导致加载时线程池不足,此时.NET无法添加更多线程来解除当前工作的阻塞
.ConfigureAwait(false)
使用await
时意味着如果任务
不完整,则不需要在当前同步上下文
上运行以下代码,这也会导致前面提到的死锁情况
人们经常存在的一个疏忽是,当任务
已经完成时,它实际上什么也不做,例如任务.FromResult
,或者您有一些缓存的结果。在这种情况下,SynchronizationContext
会进一步流入您的代码,从而增加后续代码在异步任务仍然存在时阻塞该任务的可能性,从而导致死锁
回到您的示例,无论您在代码中放入多少.ConfigureAwait(false)
,如果在await
之前运行的代码对异步代码进行了一些阻塞,那么您仍然容易发生死锁。假设死锁的来源发生在事件日
(不要这样做),这也可以为您修复死锁:
公共异步任务GetEventsForDay(字符串dayKey)
{
CheckCacheKeyExists(dayKey);
var cachedItem=await\u cache.GetOrCreateAsync(dayKey,async entry=>
{
wait Task.Delay(100).configurewait(false);//这也会停止死锁
entry.SetOptions(_cacheEntryOptions);
var事件=等待事件日(dayKey);
返回事件;
});
返回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;
}
公共异步任务GetEventsForDay(字符串dayKey)
{
CheckCacheKeyExists(dayKey);
var cachedItem=await\u cache.GetOrCreateAsync(dayKey,async entry=>
{
SynchronizationContext.SetSynchronizationContext(null);//这也会停止它
entry.SetOptions(_cacheEntryOptions);
var事件=等待事件日(dayKey);
返回事件;
});
返回cachedItem;
}
我可以想象GetOrCreateAsync
调用表达式时没有ConfigureAwait(false)
,因此尝试在当前(阻塞的)线程上恢复。ASP.NET核心没有同步上下文,因此ConfigureAwait(false)
没有任何效果:如何声明\u cacheEntryOptions
?它在哪里死锁?它是否会在调用GetEventsForDay(…)
的位置死锁?或者在wait\u cache.GetOrCreateAsync(…)
?这里还有另一个潜在问题。您的\u缓存是什么类型的?如果GetOrAddAsync()
可能会多次调用委托,那么您将启动多个任务AsyncLazy
将帮助您避免这种情况。我不喜欢这两种解决方案,有没有人能提出另一种想法?但我确实喜欢这个理论,这和我们接受专业人士的教育是一样的。不管怎样,谢谢你。没问题,你不喜欢它们很好,它们是为了说明这是如何工作的,因此“不要这样做”。修复方法是从事件日
中删除死锁场景,您需要共享死锁场景的代码,包括调用的任何第三方代码。@Stuart按照您的建议执行“错误”是什么?或者你是说找到死锁的根本原因比贴上绷带更好?正是这样,你正在努力解决这个问题并消除死锁,但如果你想让你的应用程序扩展,这可能会反过来影响你。@Stuart,你说,还有一个问题“当任务已经完成时,它实际上什么也不做,例如Task.FromResult”。为了澄清,“complete”任务可以在调用堆栈上的任何位置,对吗?因此,即使我有configurewait(false)
无处不在
private async Task<IEnumerable<Document>> EventsForDay(string dayKey)
/// <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;
}
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;
}