Imagine several tasks trying to use a pool of resources concurrently. A single resource from the pool can only be used by an specific number of tasks at time; the number can be one.
In a synchronous environment, it seems to me that WaitHandle.WaitAny
& Semaphore
is the way to go.
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();
}
But what should we do in an asynchronous environment?
I generally recommend that developers separate the "pooling" logic from the "using" logic. One nice side benefit of that separation is that only the pooling logic requires synchronization.
In the real scenario the number of resources would be run-time known; more precisely it would be at the initialization of the application (i.e. configuration).
Each port on the server accepts only one client at time and each server has only one port available.
So, you have a finite set of resources, and each resource can only be used by one thread at a time.
Since you can't create new resources on-demand, you'll need a signal to know when one is available. You can do this yourself, or you can use something like a BufferBlock<T>
to act as an async-ready queue.
Since each resource can only be used by one thread at a time, I recommend using the common IDisposable
technique for freeing the resource back to the pool.
Putting these together:
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);
}
}
}
Usage:
using (var resource = Pool.Allocate())
UseResource(resource);
or:
using (var resource = await Pool.AllocateAsync())
await UseResourceAsync(resource);
Any concerns remaining? Leave a comment.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With