【问题标题】:How can I make tasks wait for the same result?如何让任务等待相同的结果?
【发布时间】:2019-04-04 14:41:45
【问题描述】:

我有一个简单的单例类,

public class SimpleSingleton
{
    public async Task<int> GetRefreshedValue()
    {
        /*
            What goes here?
        */
        return await GetRefreshedValueImplementation();
        /*
            What goes here?
        */
    }

    private async Task<int> GetRefreshedValueImplementation()
    {
        /*
           Resource intensive and not thread safe
        */
    }
}

因为这是一个单例,GetRefreshedValue 将被同时调用。我希望一次只执行一个或零个任务GetRefreshedValueImplementation。

这本身很简单,我可以使用SemaphoreSlim。

private static SemaphoreSlim gate = new SemaphoreSlim(1);
...
await gate.WaitAsync();
try
{
    return await GetRefreshedValueImplementation();
}
finally
{
    gate.Release();
}

但是,我希望在门口等待的每个任务都能获得最近计算的返回值。我不希望他们排队打电话。

编写该代码的最佳方式是什么?

【问题讨论】:

  • "最近计算的返回值" 什么意思?您只想计算一次,或者您有重新计算的规则?
  • @Bombinosh 我希望在内部任务启动后调用的所有调用者与原始调用者一起等待内部任务的结果。
  • 您是否考虑过使用System.Lazy<T> 存储计算值这样简单的方法?
  • @JohnWu 然后它将被永远重复使用,而不是直到操作完成。
  • @JohnWu 是的和AsyncLazy&lt;T&gt; 但是,我怎么会有一个只持续方法调用的懒惰。

标签: c# multithreading async-await


【解决方案1】:

所以操作本身就足够简单了。您只需要在启动时为操作存储Task,并在完成时清除它,以便您可以在运行时重新使用任务。从那里它只是添加正确的同步,以便从多个线程中安全使用(我认为这是必需的,并且这并不是全部都通过单个同步上下文,如果是,您可以去掉锁定代码。)

public class Foo<T> //TODO come up with good name
{
    private Func<Task<T>> factory;
    private Task<T> currentInvocation;
    private object key = new object();
    public Foo(Func<Task<T>> factory)
    {
        this.factory = factory;
    }
    public Task<T> Value
    {
        get
        {
            lock (key)
            {
                if (currentInvocation == null)
                {
                    currentInvocation = factory();
                    currentInvocation?.ContinueWith(_ =>
                    {
                        lock (key) { currentInvocation = null; }
                    });
                }
                return currentInvocation;
            }
        }
    }
}

【讨论】:

    【解决方案2】:
    public class SimpleSingleton
    {
        private static Task<int> executingTask;
        private static object lockObject = new object();
    
        public async Task<int> GetRefreshedValue()
        {
            lock (lockObject)
                {
                    if (executingTask == null || executingTask.IsCompleted)
                    {
                        executingTask = GetRefreshedValueImplementation();
                    }
                }
            return await executingTask;
        }
    
        private async Task<int> GetRefreshedValueImplementation()
        {
            /*
               Resource intensive and not thread safe
            */
        }
    }
    

    【讨论】:

      【解决方案3】:

      根据我对您的情况的理解,您需要让调用获得相同正在进行的任务的结果,如果没有,则应创建一个新的。如果是这样的话,这将达到您的目的:

      public class SimpleSingleton
      {
          private SimpleSingleton() { }
          private static SimpleSingleton _instance;
          public static SimpleSingleton Instance => _instance ?? (_instance = new SimpleSingleton());
          public async Task<int> GetRefreshedValue()
          {
              return await GetRefreshedValueImplementation();
          }
          private volatile Task<int> _getRefreshedValueImplementationTask;
          private Task<int> GetRefreshedValueImplementation()
          {
              if (_getRefreshedValueImplementationTask is null || _getRefreshedValueImplementationTask.IsCompleted)
              {
                  return _getRefreshedValueImplementationTask = Task.Run(async () =>
                  {
                      /*
                         Resource intensive and not thread safe
                      */
                      int r = new Random().Next(1000, 2001);
                      await Task.Delay(r);
                      return r;
                  });
              }
              return _getRefreshedValueImplementationTask;
          }
      }
      

      【讨论】:

        【解决方案4】:

        类似:

        public class SimpleSingleton
        {
            private int _sequenceNo;
            private int _lastvalue;
            private object _lock = new object;
        
            public async Task<int> GetRefreshedValue()
            {
                var currentSeq = _sequenceNo;
                lock(_lock)
                {
                   if (_sequenceNo == currentSeq)
                   {
                      _lastValue = await GetRefreshedValueImplementation();
                      _sequenceNo++;
                   }
                }
                return _lastValue;
            }
        
            private async Task<int> GetRefreshedValueImplementation()
            {
                /*
                   Resource intensive and not thread safe
                */
            }
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2023-03-08
          • 2013-06-16
          • 2018-08-17
          • 2017-06-02
          • 1970-01-01
          相关资源
          最近更新 更多