如何使用ConcurrentDictionary的任务

时间:2015-09-24 03:00:13

标签: c# asynchronous task-parallel-library concurrent-collections

我必须编写一个程序,我从数据库中读取要处理的队列,并且所有队列都是并行运行的,并使用ConcurrentDictionary在父线程上进行管理。 我有一个表示队列的类,它有一个构造函数,它接收队列信息和父实例句柄。队列类还具有处理队列的方法。

这是队列类:

Class MyQueue { 
protected ServiceExecution _parent;
protect string _queueID;

public MyQueue(ServiceExecution parentThread, string queueID)
{
_parent = parentThread;
_queueID = queueID;
}
public void Process()
{
    try
    {
       //Do work to process
    }
    catch()
    {
       //exception handling
    }
    finally{
       _parent.ThreadFinish(_queueID);
    }

父线程遍历队列的数据集并实例化新的队列类。它产生一个新线程,以异步方式执行Queue对象的Process方法。该线程被添加到ConcurrentDictionary中,然后按如下方式启动:

private ConcurrentDictionary<string, MyQueue> _runningQueues = new ConcurrentDictionary<string, MyQueue>();

Foreach(datarow dr in QueueDataset.rows)
{
   MyQueue queue = new MyQueue(this, dr["QueueID"].ToString());
   Thread t = new Thread(()=>queue.Process());
   if(_runningQueues.TryAdd(dr["QueueID"].ToString(), queue)
   {
       t.start();
   }
}

//Method that gets called by the queue thread when it finishes
public void ThreadFinish(string queueID)
{
    MyQueue queue;
    _runningQueues.TryRemove(queueID, out queue);
}

我觉得这不是管理异步队列处理的正确方法,我想知道是否可能遇到这种设计的死锁?此外,我想使用Tasks来异步运行队列而不是新的线程。我需要跟踪队列,因为如果之前的运行尚未完成,我将不会为同一队列生成新的线程或任务。处理这种并行性的最佳方法是什么?

提前致谢!

1 个答案:

答案 0 :(得分:2)

关于您当前的方法

确实,这不是正确的做法。从数据库读取的大量队列将产生大量可能不好的线程。您将每次创建一个新线程。最好创建一些线程,然后重新使用它们。如果您想要任务,最好创建LongRunning任务并重新使用它们。

建议设计

我建议采用以下设计:

  1. 只保留一个任务来从数据库中读取队列并将这些队列放在BlockingCollection中;
  2. 现在启动多个LongRunning任务,从BlockingCollection中读取每个队列并处理该队列;
  3. 当一个任务完成处理从BlockingCollection获取的队列后,它将从该BlockingCollection获取另一个队列;
  4. 优化这些处理任务的数量,以便正确利用CPU的核心。通常,由于数据库交互很慢,因此您可以创建比核心数量多3倍的任务,但是YMMV。
  5. 死锁可能性

    它们至少不会发生在应用程序端。但是,由于队列是数据库事务,因此死锁可能发生在数据库端。如果数据库由于死锁而将其回滚,则可能必须编写一些逻辑以使您的任务再次启动事务。

    示例代码

    private static void TaskDesignedRun()
    {
        var expectedParallelQueues = 1024; //Optimize it. I've chosen it randomly
        var parallelProcessingTaskCount = 4 * Environment.ProcessorCount; //Optimize this too.
        var baseProcessorTaskArray = new Task[parallelProcessingTaskCount];
        var taskFactory = new TaskFactory(TaskCreationOptions.LongRunning, TaskContinuationOptions.None);
    
        var itemsToProcess = new BlockingCollection<MyQueue>(expectedParallelQueues);
    
        //Start a new task to populate the "itemsToProcess"
        taskFactory.StartNew(() =>
        {
            // Add code to read queues and add them to itemsToProcess
            Console.WriteLine("Done reading all the queues...");
            // Finally signal that you are done by saying..
            itemsToProcess.CompleteAdding();
        });
    
        //Initializing the base tasks
        for (var index = 0; index < baseProcessorTaskArray.Length; index++)
        {
            baseProcessorTaskArray[index] = taskFactory.StartNew(() =>
            {
                while (!itemsToProcess.IsAddingCompleted && itemsToProcess.Count != 0)           {
                    MyQueue q;
                    if (!itemsToProcess.TryTake(out q)) continue;
                    //Process your queue
                }
             });
         }
    
         //Now just wait till all queues in your database have been read and processed.
         Task.WaitAll(baseProcessorTaskArray);
    }