【问题标题】:C# async lock get the same result without code executionC#异步锁在不执行代码的情况下获得相同的结果
【发布时间】:2020-05-07 15:53:35
【问题描述】:

我有一个基于 API 调用返回一些值的方法,该 API 限制了您在每个时间段内可以执行的调用量。我需要从多个线程访问此调用的结果。现在我有以下代码:

class ReturningSemaphoreLocker<TOutput>
{
    private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);

    public async Task<T> LockAsync<T>(Func<Task<T>> worker)
    {
        await _semaphore.WaitAsync();
        try
        {
            return await worker();
        }
        finally
        {
            _semaphore.Release();
        }
    }
}

使用示例:

...
private static readonly ReturningSemaphoreLocker<List<int>> LockingSemaphore = new ReturningSemaphoreLocker<List<int>>();
...
public async Task<List<int>> GetStuff()
{
    return await LockingSemaphore.LockAsync(async () =>
    {
        var client = _clientFactory.CreateClient("SomeName");

        using (var cts = GetDefaultRequestCts())
        {
            var resp = await client.GetAsync("API TO QUERY URL", cts.Token);

            var jsonString = await resp.Content.ReadAsStringAsync();

            var items = JsonConvert.DeserializeObject<List<int>>(jsonString);

            return items;
        }
    });
}

所以问题是:如果 GetStuff() 已经在运行 再次查询 API 并再次查询 API,如果此时方法没有运行,我如何从 GetStuff() 获得相同的结果?

【问题讨论】:

  • 看看Lazy&lt;&gt;。它可以是线程安全的,所有调用都会阻塞,直到它第一次返回,然后所有调用都会被释放。

标签: c# multithreading .net-core async-await


【解决方案1】:

这里的诀窍是抓住Task&lt;T&gt;,这是不完整的结果;考虑以下完全未经测试的方法 - _inProgress 字段是这里的关键:

private static readonly ReturningSemaphoreLocker<List<int>> LockingSemaphore = new ReturningSemaphoreLocker<List<int>>();
...
private Task<List<int>> _inProgress;
public Task<List<int>> GetStuffAsync()
{
    if (_inProgress != null) return _inProgress;
    return _inProgress = GetStuffImplAsync();
}
private async Task<List<int>> GetStuffImplAsync()
{
    var result = await LockingSemaphore.LockAsync(async () =>
    {
        var client = _clientFactory.CreateClient("SomeName");

        using (var cts = GetDefaultRequestCts())
        {
            var resp = await client.GetAsync("API TO QUERY URL", cts.Token);

            var jsonString = await resp.Content.ReadAsStringAsync();

            var items = JsonConvert.DeserializeObject<List<int>>(jsonString);

            return items;
        }
    });
    // this is important so that if everything turns
    // out to be synchronous, we don't nuke the _inProgress field *before*
    // it has actually been set
    await Task.Yield();

    // and now wipe the field since we know it is no longer in progress;
    // the next caller should actually try to do something interesting
    _inProgress = null;

    return result;
}

【讨论】:

    【解决方案2】:

    这是一个可用于基于时间的限制的类,而不是 ReturningSemaphoreLocker

    class ThrottledOperation
    {
        private readonly object _locker = new object();
        private readonly Stopwatch _stopwatch = Stopwatch.StartNew();
        private Task _task;
    
        public Task<T> GetValueAsync<T>(Func<Task<T>> taskFactory, TimeSpan interval)
        {
            lock (_locker)
            {
                if (_task != null && (_stopwatch.Elapsed < interval || !_task.IsCompleted))
                {
                    return (Task<T>)_task;
                }
                _task = taskFactory();
                _stopwatch.Restart();
                return (Task<T>)_task;
            }
        }
    }
    

    GetValueAsync 方法返回相同的任务,直到经过限制间隔并且任务已完成。此时,它使用提供的任务工厂方法创建并返回一个新任务。

    使用示例:

    private static readonly ThrottledOperation _throttledStuff = new ThrottledOperation();
    
    public Task<List<int>> GetStuffAsync()
    {
        return _throttledStuff.GetValueAsync(async () =>
        {
            var client = _clientFactory.CreateClient("SomeName");
            using (var cts = GetDefaultRequestCts())
            {
                var resp = await client.GetAsync("API TO QUERY URL", cts.Token);
                var jsonString = await resp.Content.ReadAsStringAsync();
                var items = JsonConvert.DeserializeObject<List<int>>(jsonString);
                return items;
            }
        }, TimeSpan.FromSeconds(30));
    }
    

    【讨论】:

      猜你喜欢
      • 2018-06-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-07-28
      • 2019-06-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多