我有一个ConcurrentStack,我将物品转储到。在堆栈不空的情况下,一次处理这些项目的好方法是什么?我希望这样做的方式是在没有处理堆栈时不会占用CPU周期。
我目前得到的基本上是这个,它似乎不是一个理想的解决方案。
private void AddToStack(MyObj obj)
{
stack.Push(obj);
HandleStack();
}
private void HandleStack()
{
if (handling)
return;
Task.Run( () =>
{
lock (lockObj)
{
handling = true;
if (stack.Any())
{
//handle whatever is on top of the stack
}
handling = false;
}
}
}
所以bool就在那里,所以多个线程都没有备份等待锁定。但我不希望一次处理堆栈的多个事情因此锁定。因此,如果两个单独的线程最终同时调用HandleStack并通过bool,则锁定就在那里,因此两者都不会立即迭代堆栈。但是一旦第二次通过锁定,堆栈就会变空,并且什么都不做。所以这最终会给我我想要的行为。
所以我真的只是在ConcurrentStack周围写一个伪并发包装器,似乎必须采用不同的方法来实现这一目标。想法?
答案 0 :(得分:3)
您可以考虑使用Microsoft TPL Dataflow来执行此类操作。
这是一个显示如何创建队列的简单示例。尝试一下,然后使用MaxDegreeOfParallelism
和BoundedCapacity
的设置来查看会发生什么。
对于您的示例,如果您不希望同时处理数据项的多个线程,我认为您将要将MaxDegreeOfParallelism
设置为1。
(注意:您需要使用.Net 4.5x并使用Nuget为项目安装TPL Dataflow。)
同时阅读Stephen Cleary's blog about TPL。
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
namespace SimpleTPL
{
class MyObj
{
public MyObj(string data)
{
Data = data;
}
public readonly string Data;
}
class Program
{
static void Main()
{
var queue = new ActionBlock<MyObj>(data => process(data), actionBlockOptions());
var task = queueData(queue);
Console.WriteLine("Waiting for task to complete.");
task.Wait();
Console.WriteLine("Completed.");
}
private static void process(MyObj data)
{
Console.WriteLine("Processing data " + data.Data);
Thread.Sleep(200); // Simulate load.
}
private static async Task queueData(ActionBlock<MyObj> executor)
{
for (int i = 0; i < 20; ++i)
{
Console.WriteLine("Queuing data " + i);
MyObj data = new MyObj(i.ToString());
await executor.SendAsync(data);
}
Console.WriteLine("Indicating that no more data will be queued.");
executor.Complete(); // Indicate that no more items will be queued.
Console.WriteLine("Waiting for queue to empty.");
await executor.Completion; // Wait for executor queue to empty.
}
private static ExecutionDataflowBlockOptions actionBlockOptions()
{
return new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = 4,
BoundedCapacity = 8
};
}
}
}
答案 1 :(得分:2)
看起来你想要一个典型的制作人消费者。
我建议使用autoresetevent
让您的消费者在堆栈为空时等待。调用producer方法时调用Set。
阅读此主题
Fast and Best Producer/consumer queue technique BlockingCollection vs concurrent Queue
答案 2 :(得分:2)
ConcurrentStack<T>
是实现IProducerConsumerCollection<T>
的集合之一,因此可以由BlockingCollection<T>
包装。 BlockingCollection<T>
有几个便利成员用于常见操作,例如&#34;在堆栈不为空时消耗&#34;。例如,您可以在循环中调用TryTake
。或者,您可以使用GetConsumingEnumerable
:
private BlockingCollection<MyObj> stack;
private Task consumer;
Constructor()
{
stack = new BlockingCollection<MyObj>(new ConcurrentStack<MyObj>());
consumer = Task.Run(() =>
{
foreach (var myObj in stack.GetConsumingEnumerable())
{
...
}
});
}
private void AddToStack(MyObj obj)
{
stack.Add(obj);
}