【问题标题】:Deadlock on Async lazy with PublicationOnly使用 PublicationOnly 导致异步延迟死锁
【发布时间】:2021-05-15 06:43:07
【问题描述】:

假设以下代码

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 使用我的逻辑工作?

【问题讨论】:

  • 如果在获取一个缓存值时没有缓存值,并且您尝试从服务器获取一个但失败了,您想做什么?你应该返回什么?
  • 这个 GetTS().ConfigureAwait(false).GetAwaiter().GetResult() 叫死锁!
  • 失败时,我想返回异常,并在下一个请求时重试... LazyThreadSafetyMode.PublicationOnly - 没有异步提供了开箱即用的功能...
  • @Nick,我明白了,如果我将 ConfigureAwait 移到 GetTS 中,它会解决问题...但我不能...
  • 您可能会觉得这很有趣:Enforce an async method to be called once。它包括一个不使用Lazy&lt;T&gt; 类的实现。

标签: c# asp.net-mvc async-await task-parallel-library


【解决方案1】:

问题其实与Lazy&lt;T&gt;无关。死锁是因为它是blocking on asynchronous code

在这段代码中:

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

ConfigureAwait(false) 什么都不做。 ConfigureAwait 配置等待,而不是任务,而且那里没有await

最好的选择是去async all the way。如果担心异常,您可以使用AsyncLazy&lt;T&gt; 并传递AsyncLazyFlags.RetryOnFailure

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

【讨论】:

  • 感谢@StephenCleary,这真的很有意义。不知道 AsyncLazyFlags.RetryOnFailure,这正是我要问的。我知道问题不在于 Lazy 方面,但并非所有 FCL 课程都容易适合异步人员
【解决方案2】:

实际上我发现没有简单的方法可以做到这一点,但是 .net github 中的功能请求讨论给出了当前情况的完整图景

GitHub: Add async support to System.Lazy

实际上,上一个 @StephenCleary 的第 5 点回答了我关于异常缓存的问题

  1. 异常重置也是一个重要的用例。我已经解决了 这是通过添加另一个异步惰性标志来重置 如果抛出异常,则将 AsyncLazy 设置为未初始化状态 代表。所有现有的访问者都会看到异常,但下一个 访问者将重试委托。

【讨论】:

    猜你喜欢
    • 2021-09-18
    • 1970-01-01
    • 1970-01-01
    • 2017-02-18
    • 2020-01-12
    • 1970-01-01
    相关资源
    最近更新 更多