在异步环境中共享资源池的有效方法是什么?

时间:2015-07-26 17:55:36

标签: c# .net asynchronous

想象一下尝试同时使用资源池的几个任务。池中的单个资源只能由特定数量的任务使用;数字可以是一个。

同步环境中,在我看来WaitHandle.WaitAny & Semaphore是可行的方法。

var resources = new[] { new Resource(...), new Resource(...) }; // 'Resource' custom class wrapers the resource
var semaphores = new[] { new Semaphore(1, 1), new Semaphore(1, 1) };
... 
var index = WaitHandle.WaitAny(semaphores);

try
{
    UseResource(resources[index]);
}
finally
{
    semaphores[index].Release();
}

但是我们应该在异步环境中做些什么呢?

4 个答案:

答案 0 :(得分:5)

我通常建议开发人员将“池”逻辑与“使用”逻辑分开。这种分离的一个好处是,只有池化逻辑需要同步。

  

在真实场景中,资源的数量将是运行时已知的;更准确地说,它将在应用程序的初始化(即配置)。

     

服务器上的每个端口一次只接受一个客户端,每个服务器只有一个端口可用。

因此,您拥有一组有限的资源,每个资源一次只能由一个线程使用。

由于您无法按需创建新资源,因此您需要一个信号来了解何时可用。您可以自己执行此操作,也可以使用BufferBlock<T>之类的内容作为异步就绪队列。

由于每个资源一次只能由一个线程使用,我建议使用通用的IDisposable技术将资源释放回池中。

将这些放在一起:

public sealed class Pool
{
  private readonly BufferBlock<Resource> _block = new BufferBlock<Resource>();

  public Pool()
  {
    _block.Post(new Resource(this, ...));
    _block.Post(new Resource(this, ...));
  }

  public Resource Allocate()
  {
    return _block.Receive();
  }

  public Task<Resource> AllocateAsync()
  {
    return _block.ReceiveAsync();
  }

  private void Release(Resource resource)
  {
    _block.Post(resource);
  }

  public sealed class Resource : IDisposable
  {
    private readonly Pool _pool;
    public Resource(Pool pool, ...)
    {
      _pool = pool;
      ...
    }

    public void Dispose()
    {
      _pool.Release(this);
    }
  }
}

用法:

using (var resource = Pool.Allocate())
    UseResource(resource);

或:

using (var resource = await Pool.AllocateAsync())
    await UseResourceAsync(resource);

答案 1 :(得分:2)

  1. 将“WaitAny”样式逻辑封装到帮助器中。这使得代码再次感觉自然。这解决了混乱。通常,由于等待,异步代码在结构上看起来与同步版本完全相同。
  2. 关于性能,这应该比同步等待句柄执行得更好,因为它们需要内核模式转换和上下文切换。确保不依赖于控制流的异常(取消),因为它们非常慢(例如每个例外0.1us)。
  3. 还有任何疑虑吗?发表评论。

答案 2 :(得分:0)

我可能会在对象上使用mutexlock。要么强制线程等到锁定或互斥锁被释放。

答案 3 :(得分:0)

遵循异步版本Task.WhenAny & SemaphoreSlim

var resources = new[] { new Resource(...), new Resource(...) }; // 'Resource' custom class wrapers the resource
var semaphores = new[] { new SemaphoreSlim(1, 1), new SemaphoreSlim(1, 1) };
...
var waits = new[] { semaphores[0].WaitAsync(), semaphores[1].WaitAsync() };

var index = Array.IndexOf(waits, await Task.WhenAny(waits));

// The wait is still running - perform compensation.
if (index == 0)
    waits[1].ContinueWith(_ => semaphores[1].Release());
else if (index == 1)
    waits[0].ContinueWith(_ => semaphores[0].Release());

try
{
    await UseResourceAsync(resources[index]);
}
finally
{
    semaphores[index].Release();
}