繁体   English   中英

为什么 Task.Run 中的这种包装会从 asp.net 核心中移除死锁?

[英]Why does this wrapping in Task.Run remove deadlock from asp.net core?

我正在尝试修复一种方法,该方法应该使用内置的GetOrCreateAsyncMemoryCache检索任意数据。 这是 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中的所有异步操作都是awaitConfigureAwait(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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM