C#BlockingCollection生成者使用者而不阻塞使用者线程

时间:2014-07-18 14:42:59

标签: c# .net blockingcollection

我有一种情况,我需要有大量(数百)队列,其中的项目应按顺序处理(需要单线程消费者)。我的第一个实现based on the samples,我使用了一个长时间运行的Task per BlockingCollection来使用队列项。但是,我最终得到了一个拥有数百个线程的应用程序,这些线程大部分时间都处于空闲状态而只消耗内存,因为大多数时候队列都是空的。

我认为只有当队列中的某些东西要处理时才会运行消费者任务会更好,但是,我还没有能够找到能够提供最佳实践的示例是

我提出了类似于下面的解决方案。但问题是,每个项目都会产生一个新任务(这可能是效率低下的?浪费资源?)。但如果我没有为每件商品创建新任务,我就无法保证某件商品不会在未经处理的队列中排队。

    private object _processSyncObj = new object();
    private volatile bool _isProcessing;
    private BlockingCollection<string> _queue = new BlockingCollection<string>();

    private void EnqueueItem(string item)
    {
        _queue.Add(item);
        Task.Factory.StartNew(ProcessQueue);
    }

    private void ProcessQueue()
    {
        if (_isProcessing)
            return;

        lock (_processSyncObj)
        {
             string item;
             while (_isProcessing = _queue.TryTake(out item))
             {
                 // process item
             }
        }
    }

针对这种情况的最佳做法/最佳解决方案是什么,并保证项目不在队列中,但没有消费者在运行?

3 个答案:

答案 0 :(得分:5)

我认为你所做的是合理的,因为任务也可以很好地扩展到数百万个任务,针对ThreadPool产生内部子队列,避免过多的上下文切换。

  

在幕后,任务排队到ThreadPool,后者已经使用算法进行了增强,这些算法可以确定并调整线程数,并提供负载平衡以最大化吞吐量。这使得任务相对轻量级,您可以创建其中的许多任务以实现细粒度的并行性。

Task Parallelism (Task Parallel Library)

...但是你做了什么,最终将只是一个正常的任务编程,因为对于每个入队你开始一个任务,所以阻塞集合是非常未使用的。据了解,您关心的是解雇任务,让TaskScheduler在工作到达时按顺序运行。

您知道吗?您也可以自定义TaskScheduler

如何使用Task编程模式,再加上自定义TaskScheduler来控制计划任务的流程呢?

例如,您可以创建一个OrderedTaskScheduler,它派生自一个行为类似的LimitedConcurrencyLevelTask​​Scheduler ......

  

LimitedConcurrencyLevelTask​​Scheduler 类提供了一个任务调度程序,可确保在ThreadPool上运行时具有最大并发级别。有必要设置此调度程序所需的最大并行度。

     

OrderedTaskScheduler 类提供了一个任务调度程序,可确保一次只执行一个任务。任务按排队顺序(FIFO)执行。它是LimitedConcurrencyLevelTask​​Scheduler的子类,它将1作为其基类构造函数的参数发送。

您可以找到已经开发的这些调度程序,它们被称为ParallelExtensionsExtras,您可以从here下载它,并从此blog post和{{ {3}}

您也可以直接在others上找到它,并在nuget上找到代码镜像。

享受! :)

答案 1 :(得分:1)

您考虑过Parallel Extension Extras了吗?我相信QueuedTaskScheduler or ThreadPerTaskScheduler可以轻松满足您的方案。

答案 2 :(得分:1)

当然是相当重写,但你是否考虑过这样做呢?

public class WorkerQueue<T>
{
    public WorkerQueue(Action<T> workerMethod)
    {
        _workerMethod = workerMethod;
        Task.Factory.StartNew(WorkerAction);
    }

    private Action<T> _workerMethod;

    private void WorkerAction()
    {
        lock (_processSyncObj)
        {
            if (_workerMethod == null)
                return;

            while (true)
            {
                T item;
                if (_queue.TryTake(out item))
                {
                    var method = _workerMethod;
                    if (method != null)
                        method(item);

                }
            }
        }
    }

    private BlockingCollection<T> _queue = new BlockingCollection<T>();
    private object _processSyncObj = new object();
    private volatile bool _isProcessing;

    public void EnqueueItem(T item)
    {
        // thought you might want to swap BlockingCollection with a normal collection since you apparently only want your read threadlocked? You're already making that sure in "WorkerAction"
        _queue.Add(item);
    }
}


/// <summary>
/// Usage example
/// </summary>
public class Program
{
    public void Start()
    {
        var test = new WorkerQueue<string>(WorkerMethod);
    }

    private void WorkerMethod(string s)
    {
        Console.WriteLine(s);
    }
}