首页 > 解决方案 > 使用 PublicationOnly 导致异步延迟死锁

问题描述

假设以下代码

public class ValuesController : ApiController
{
    // GET api/values
    public IEnumerable<string> Get()
    {
        Lazy<TimeSpan> lm = new Lazy<TimeSpan>(GetDataAsync1, System.Threading.LazyThreadSafetyMode.PublicationOnly);

        return new string[] { "value1", "value2", lm.Value.ToString() };
    }

    private TimeSpan GetDataAsync1()
    {

        return GetTS().ConfigureAwait(false).GetAwaiter().GetResult();

    }

    // I Cant change this method, and what is inside it...
    private async Task<TimeSpan> GetTS()
    {
        var sw = Stopwatch.StartNew();

        using (var client = new HttpClient())
        {
            var result = await client.GetAsync("https://www.google.com/");
        }

        sw.Stop();
        return sw.Elapsed;
    }
}

关键是我正在从远程服务器获取一些数据,并希望将其缓存以备后用。由于远程服务器可能在给定点失败,我不想缓存异常,但只有成功结果......所以保持而不是等待值对我不起作用

// Cant use this, because this caches failed exception as well
Lazy<Task...> lz = ...
await lz.Value

但是上面剪断了,正如预期的那样会产生死锁,因为我无法更改 GetTS,是否可以强制 Lazy 使用我的逻辑工作?

标签: c#asp.net-mvcasync-awaittask-parallel-library

解决方案


这个问题实际上与Lazy<T>. 死锁是因为它阻塞了异步代码

在这段代码中:

private TimeSpan GetDataAsync1()
{
  return GetTS().ConfigureAwait(false).GetAwaiter().GetResult();
}

ConfigureAwait(false)什么都不做。ConfigureAwait配置awaits,而不是tasks,并且那里没有await

最好的选择是async一路走。如果异常是一个问题,您可以使用AsyncLazy<T>并传递AsyncLazyFlags.RetryOnFailure.

如果你不能async一路走,下一个最好的选择是一路同步。如果您不能执行其中任何一项,那么您将不得不选择sync-over-async hack;请注意,没有适用于所有情况的 hack。


推荐阅读