我正在尝试使用BlockingCollection<T>
实现生产者/消费者模式,所以我编写了一个简单的控制台应用程序来测试它。
public class Program
{
public static void Main(string[] args)
{
var workQueue = new WorkQueue();
workQueue.StartProducingItems();
workQueue.StartProcessingItems();
while (true)
{
}
}
}
public class WorkQueue
{
private BlockingCollection<int> _queue;
private static Random _random = new Random();
public WorkQueue()
{
_queue = new BlockingCollection<int>();
// Prefill some items.
for (int i = 0; i < 100; i++)
{
//_queue.Add(_random.Next());
}
}
public void StartProducingItems()
{
Task.Run(() =>
{
_queue.Add(_random.Next()); // Should be adding items to the queue constantly, but instead adds one and then nothing else.
});
}
public void StartProcessingItems()
{
Task.Run(() =>
{
foreach (var item in _queue.GetConsumingEnumerable())
{
Console.WriteLine("Worker 1: " + item);
}
});
Task.Run(() =>
{
foreach (var item in _queue.GetConsumingEnumerable())
{
Console.WriteLine("Worker 2: " + item);
}
});
}
}
然而,我的设计存在3个问题:
我不知道在我的Main方法中阻塞/等待的正确方法。做一个简单的空while循环似乎非常低效,并且CPU使用浪费只是为了确保应用程序不会结束。
我的设计还存在另一个问题,在这个简单的应用程序中,我有一个无限期生成项目的生产者,应该永远不会停止。在真实世界的设置中,我希望它最终结束(例如,用完处理的文件)。在那种情况下,我应该如何等待它在Main方法中完成?制作StartProducingItems
async
然后await
吗?
GetConsumingEnumerable
或Add
无法正常工作。制作人应该不断添加项目,但它会添加一个项目,然后再也不会添加。然后由一个消费者处理这一项。两个消费者然后阻止等待添加的项目,但没有一个。我知道Take
方法,但在while循环中再次在Take
上旋转似乎非常浪费和低效。有一个CompleteAdding
方法,但是不允许添加任何其他内容并在尝试时抛出异常,因此不合适。
我确信两个消费者实际上都在阻塞并等待新项目,因为我可以在调试期间在线程之间切换:
编辑:
我已在其中一条评论中提出了更改,但Task.WhenAll
仍然立即返回。
public Task StartProcessingItems()
{
var consumers = new List<Task>();
for (int i = 0; i < 2; i++)
{
consumers.Add(Task.Run(() =>
{
foreach (var item in _queue.GetConsumingEnumerable())
{
Console.WriteLine($"Worker {i}: " + item);
}
}));
}
return Task.WhenAll(consumers.ToList());
}
答案 0 :(得分:4)
GetConsumingEnumerable()
正在阻止。如果要不断添加到队列中,则应将调用_queue.Add
置于循环中:
public void StartProducingItems()
{
Task.Run(() =>
{
while (true)
_queue.Add(_random.Next());
});
}
关于Main()
方法,您可以调用Console.ReadLine()
方法以防止在按下某个键之前完成主线程:
public static void Main(string[] args)
{
var workQueue = new WorkQueue();
workQueue.StartProducingItems();
workQueue.StartProcessingItems();
Console.WriteLine("Press a key to terminate the application...");
Console.ReadLine();
}