我正在尝试使用一组资源实现生产者/消费者模式,因此每个线程都有一个与之关联的资源。例如,我可能有一个任务队列,其中每个任务都需要StreamWriter
来写入其结果。每个任务还必须传递参数。
我开始使用Joseph Albahari的实现(请参阅下面的修改版本)。
我将Action
的队列替换为Action<T>
的队列,其中T
是资源,并将与该线程关联的资源传递给Action
。但是,这让我遇到了如何将参数传递给Action
的问题。显然,Action
必须用委托替换,但这会留下如何在任务入队时(从ProducerConsumerQueue
类之外)传递参数的问题。关于如何做到这一点的任何想法?
class ProducerConsumerQueue<T>
{
readonly object _locker = new object();
Thread[] _workers;
Queue<Action<T>> _itemQ = new Queue<Action<T>>();
public ProducerConsumerQueue(T[] resources)
{
_workers = new Thread[resources.Length];
// Create and start a separate thread for each worker
for (int i = 0; i < resources.Length; i++)
{
Thread thread = new Thread(() => Consume(resources[i]));
thread.SetApartmentState(ApartmentState.STA);
_workers[i] = thread;
_workers[i].Start();
}
}
public void Shutdown(bool waitForWorkers)
{
// Enqueue one null item per worker to make each exit.
foreach (Thread worker in _workers)
EnqueueItem(null);
// Wait for workers to finish
if (waitForWorkers)
foreach (Thread worker in _workers)
worker.Join();
}
public void EnqueueItem(Action<T> item)
{
lock (_locker)
{
_itemQ.Enqueue(item); // We must pulse because we're
Monitor.Pulse(_locker); // changing a blocking condition.
}
}
void Consume(T parameter)
{
while (true) // Keep consuming until
{ // told otherwise.
Action<T> item;
lock (_locker)
{
while (_itemQ.Count == 0) Monitor.Wait(_locker);
item = _itemQ.Dequeue();
}
if (item == null) return; // This signals our exit.
item(parameter); // Execute item.
}
}
}
答案 0 :(得分:3)
T
中的ProducerConsumerQueue<T>
类型不一定是您的资源,它可以是包含您的资源的复合类型。使用.NET4,最简单的方法是使用Tuple<StreamWriter, YourParameterType>
。产品/消费者队列只是吃掉并吐出T
所以在你的Action<T>
中你可以使用属性来获取资源和参数。如果您使用Tuple
,则可以使用Item1
获取资源,Item2
获取参数。
如果您不使用.NET4,过程类似,但您只需创建自己的类:
public class WorkItem<T>
{
private StreamWriter resource;
private T parameter;
public WorkItem(StreamWriter resource, T parameter)
{
this.resource = resource;
this.parameter = parameter;
}
public StreamWriter Resource { get { return resource; } }
public T Parameter { get { return parameter; } }
}
事实上,使其成为通用可能会过度设计您的情况。您可以将T定义为您想要的类型。
另外,作为参考,有一些新方法可以在.NET4中包含可能适用于您的用例的多线程,例如并发队列和并行任务库。它们还可以与信号量等传统方法结合使用。
修改强>
继续这种方法,这里有一个小样本类,演示使用:
以下是Processor
类:
public class Processor
{
private const int count = 3;
private ConcurrentQueue<StreamWriter> queue = new ConcurrentQueue<StreamWriter>();
private Semaphore semaphore = new Semaphore(count, count);
public Processor()
{
// Populate the resource queue.
for (int i = 0; i < count; i++) queue.Enqueue(new StreamWriter("sample" + i));
}
public void Process(int parameter)
{
// Wait for one of our resources to become free.
semaphore.WaitOne();
StreamWriter resource;
queue.TryDequeue(out resource);
// Dispatch the work to a task.
Task.Factory.StartNew(() => Process(resource, parameter));
}
private Random random = new Random();
private void Process(StreamWriter resource, int parameter)
{
// Do work in background with resource.
Thread.Sleep(random.Next(10) * 100);
resource.WriteLine("Parameter = {0}", parameter);
queue.Enqueue(resource);
semaphore.Release();
}
}
现在我们可以使用这样的类:
var processor = new Processor();
for (int i = 0; i < 10; i++)
processor.Process(i);
并且不会同时安排三个任务,每个任务都有自己的StreamWriter
资源,这些资源将被回收。