我对如何将实现生产者使用者管道的类暴露给客户端代码感到有些困惑。
我们说我有两个代表生产者和消费者的类,如下所示:
public class Consumer
{
...
public void Cosume();
...
}
然后是制片人:
public class Producer
{
...
public void Produce();
...
}
还有第三个类可以协调生产者和消费者(顺便提一下,这就是问题)
public class ProducerConsumer
{
...
private Producer producer;
private Consumer consumer;
...
public void Start()
{
...
}
}
我该如何实施启动?
我想在Task.Run中调用producer.Produce()和consumer.Consume()并等待它们完成,如下所示:
public async Task Start()
{
await Task.WhenAll(Task.Run(() => producer.Produce(), Task.Run(() => consumer.Consume());
}
但是我已经知道在实现中使用Task.Run()并不是一个很好的做法,所以我放弃了它。
我还想到了Parallel.Invoke(),并且有责任将Parallel.Invoke()的阻塞等待卸载到另一个Thread到客户端代码,如下所示:
public void Start()
{
Parallel.Invoke(() => producer.Produce(), () => consumer.Consume());
}
客户端代码会执行以下操作:
public async void ButtonHandler(object sender, RoutedEventArgs e)
{
await Task.Run(() => producerConsumer.Start());
}
但卸载到另一个线程只是为了等待另外两个线程完成看起来有点奇怪,因为我只是浪费一个线程来等待。
在阅读StackOverflow上的其他问题后,许多人建议将如何调用并行代码到调用代码的责任,所以我想从ProducerConsumer类中公开Producer和Consumer,让客户端代码调用Producer.Produce ()和Consumer.Consume()它想要的方式,或多或少像:
public async void ButtonHandler(object sender, RoutedEventArgs e)
{
Task producerTask = Task.Run(() => producerConsumer.Producer.Produce());
Task consumerTask = Task.Run(() => producerConsumer.Consumer.Consumer());
await Task.WhenAll(producerTask, consumerTask);
}
但是这最后一个实现看起来很尴尬,因为调用者代码负责调用Produce()和Consume(),如果省略这两个方法中的一个,则可能导致错误。
我了解TPL.DataFlow来实现生产者使用者管道,但是我不能将外部依赖项添加到库中。
那我该如何编写Start()方法?
更一般地说:我应该如何将与其本质并行的代码公开给库客户端代码?
答案 0 :(得分:3)
如果您有一个async
兼容的生产者/消费者队列(例如,来自TPL Dataflow的BufferBlock<T>
),并且您的生产者受到限制(例如,队列具有合理的最大元素数,或者生产者的数据来自一些I / O操作),然后你可以让你的生产者和消费者async
直接调用它们:
public async Task ExecuteAsync()
{
await Task.WhenAll(producer.ProduceAsync(), consumer.ConsumeAsync())
.ConfigureAwait(false);
}
否则,你必须“给”某个地方。如果您的生产者和消费者是同步的,那么您的包含类(根据定义)控制两个线程。在这种情况下,可以使用Task.Run
:
public async Task ExecuteAsync()
{
await Task.WhenAll(Task.Run(() => producer.Produce()), Task.Run(() => consumer.Consume()))
.ConfigureAwait(false);
}