不为空时处理ConcurrentStack?

时间:2015-05-29 14:01:37

标签: c# multithreading concurrency

我有一个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周围写一个伪并发包装器,似乎必须采用不同的方法来实现这一目标。想法?

3 个答案:

答案 0 :(得分:3)

您可以考虑使用Microsoft TPL Dataflow来执行此类操作。

这是一个显示如何创建队列的简单示例。尝试一下,然后使用MaxDegreeOfParallelismBoundedCapacity的设置来查看会发生什么。

对于您的示例,如果您不希望同时处理数据项的多个线程,我认为您将要将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);
}