我有第三方课程,我们称之为Analyser
。这个类非常擅长分析,但是实例化并且不支持多线程是昂贵的(秒)。
我的应用程序需要提供涉及调用Analyser
的请求。这些请求将同时发生。
我想我需要创建一个通用类,比如
public class Pool<T>
{
public Pool(Func<T> instantiator, int size)
{
...
}
public async Task<TResult> Invoke<TResult>(
Func<T, TResult> target,
CancellationToken cancellationToken)
{
// await the first available T,
// lock the T,
// invoke the target, return the result
// release the lock
}
}
此类将一般性地封装池功能。
我的问题是,实现这个类的正确方法是什么。它是否已经存在不同的名称?我应该使用TPL.DataFlow
吗?我应该亲自动手吗?
好被定义为可靠的线程安全,更容易维护更好。
如果通用Pool
是解决问题的错误方法,请提供正确的替代方案。
Pool
类将使用类似的东西。
private readonly Pool<Analyser> pool = new Pool<Analyser>(
() => new Analyser(a, b, c),
100);
public async Task<string> ProcessRequest(
string raw,
CancellationToken cancellationToken)
{
return await this.pool.Invoke(
analyser => analyser.Analyse(raw),
cancellationToken);
}
答案 0 :(得分:3)
我认为构建一个通用池将是一项非常复杂的任务,因此我将会非常有趣: - )
注意:我与您的愿景不同的最重要的一点是,我不希望池处理与其管理的对象相关的线程问题。该池有一些与线程安全相关的代码,但仅用于管理它自己的状态(实例列表)。 线程启动,停止/和/或取消是池的客户端和构造对象的关注点,而不是池本身。
我会先:
超简化实施:
class PoolItem<T> : IDisposable
{
public event EventHandler<EventArgs> Disposed;
public PoolItem(T wrapped)
{
WrappedObject = wrapped;
}
public T WrappedObject { get; private set; }
public void Dispose()
{
Disposed(this, EventArgs.Empty);
}
}
现在是游泳池:
class Pool<T> where T : class
{
private static readonly object m_SyncRoot = new object();
private readonly Func<T> m_FactoryMethod;
private List<T> m_PoolItems = new List<T>();
public Pool(Func<T> factoryMethod)
{
m_FactoryMethod = factoryMethod;
}
public PoolItem<T> Get()
{
T target = null;
lock (m_SyncRoot)
{
if (m_PoolItems.Count > 0)
{
target = m_PoolItems[0];
m_PoolItems.RemoveAt(0);
}
}
if (target == null)
target = m_FactoryMethod();
var wrapper = new PoolItem<T>(target);
wrapper.Disposed += wrapper_Disposed;
return wrapper;
}
void wrapper_Disposed(object sender, EventArgs e)
{
var wrapper = sender as PoolItem<T>;
lock (m_SyncRoot)
{
m_PoolItems.Add(wrapper.WrappedObject);
}
}
}
用法:
class ExpensiveConstructionObject
{
public ExpensiveConstructionObject()
{
Console.WriteLine("Executing the expensive constructor...");
}
public void Do(string stuff)
{
Console.WriteLine("Doing: " + stuff);
}
}
class Program
{
static void Main(string[] args)
{
var pool = new Pool<ExpensiveConstructionObject>(() => new ExpensiveConstructionObject());
var t1 = pool.Get();
t1.WrappedObject.Do("task 1");
using (var t2 = pool.Get())
t2.WrappedObject.Do("task 2");
using (var t3 = pool.Get())
t3.WrappedObject.Do("task 3");
t1.Dispose();
Console.ReadLine();
}
}
接下来的步骤是:
答案 1 :(得分:3)
IIUC您尝试实现的是一个通用对象池,当您没有资源可供使用时,您需要异步等待直到您这样做。
最简单的解决方案是使用TPL Dataflow
BufferBlock
来保存项目,并在空白时等待。在您的API中,您将获得一个委托并运行它,但我建议您从池中返回实际项目,并让用户决定如何处理它:
public class ObjectPool<TItem>
{
private readonly BufferBlock<TItem> _bufferBlock;
private readonly int _maxSize;
private readonly Func<TItem> _creator;
private readonly CancellationToken _cancellationToken;
private readonly object _lock;
private int _currentSize;
public ObjectPool(int maxSize, Func<TItem> creator, CancellationToken cancellationToken)
{
_lock = new object();
_maxSize = maxSize;
_currentSize = 1;
_creator = creator;
_cancellationToken = cancellationToken;
_bufferBlock = new BufferBlock<TItem>(new DataflowBlockOptions{CancellationToken = cancellationToken});
}
public void Push(TItem item)
{
if (!_bufferBlock.Post(item) || _bufferBlock.Count > _maxSize)
{
throw new Exception();
}
}
public Task<TItem> PopAsync()
{
TItem item;
if (_bufferBlock.TryReceive(out item))
{
return Task.FromResult(item);
}
if (_currentSize < _maxSize)
{
lock (_lock)
{
if (_currentSize < _maxSize)
{
_currentSize++;
_bufferBlock.Post(_creator());
}
}
}
return _bufferBlock.ReceiveAsync();
}
}
<强>说明:强>
AsyncLock
轻松替换。PopAsync
返回Task
但不是异步方法,因此只要有要返回的项目,它就会同步完成。它仅在池为空且达到限制时才等待。您可以添加一个返回IDisposable
的方法,这样您就可以放心使用scope
而无需担心:
public async Task<Disposable> GetDisposableAsync()
{
return new Disposable(this, await PopAsync());
}
public class Disposable : IDisposable
{
private readonly ObjectPool<TItem> _pool;
public TItem Item { get; set; }
public Disposable(ObjectPool<TItem> pool, TItem item)
{
Item = item;
_pool = pool;
}
public void Dispose()
{
_pool.Push(Item);
}
}
答案 2 :(得分:-1)
游泳池是一个很好的解决方案。毕竟,一个池完全用于此目的(维护一组对象,每次实例化都太昂贵了:数据库连接,线程等等。)
但是,如果你想构建一个泛型池,你必须非常小心:你的代码用户可能做“意外”的事情,并最终自己开始射击。
锁定,例如:你应该确实检查这不会导致死锁。如果需要,可以即时扩展池,或者如果代表要求更多对象,则抛出池... 也应该谨慎对待例外情况。
因此,“等待第一个可用的T”和“锁定T”步骤应该由池完全处理,并且应该进行所有必要的检查以避免尴尬的情况。您可以考虑提供您的“客户端代码”(目标)以及对池的引用,以便在需要时提供额外的锁定功能(例如嵌套锁定或类似的东西)
更实际的是:您可以从专门针对Analyser
课程的解决方案开始,然后在需要时从那里开始使用通用池?