1

I have to write a program where I'm reading from a database the queues to process and all the queues are run in parallel and managed on the parent thread using a ConcurrentDictionary. I have a class that represents the queue, which has a constructor that takes in the queue information and the parent instance handle. The queue class also has the method that processes the queue.

Here is the Queue Class:

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);
    }

The parent thread loops through the dataset of queues and instantiates a new queue class. It spawns a new thread to execute the Process method of the Queue object asynchronously. This thread is added to the ConcurrentDictionary and then started as follows:

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);
}

I have a feeling this is not the right approach to manage the asynchronous queue processing and I'm wondering if perhaps I can run into deadlocks with this design? Furthermore, I would like to use Tasks to run the queues asynchronously instead of the new Threads. I need to keep track of the queues because I will not spawn a new thread or task for the same queue if the previous run is not complete yet. What is the best way to handle this type of parallelism?

Thanks in advance!

ptn77
  • 567
  • 3
  • 8
  • 23
  • What are these queues that you have stored in the database? – displayName Sep 24 '15 at 03:03
  • The queues are much more complicated than I described, but the main point is that I need the queue names from the database. For each queue name, I need an asynch thread or task to process the transactions in the queue. – ptn77 Sep 24 '15 at 03:07
  • Can you describe the kind of work that is being done on each queue? – Enigmativity Sep 24 '15 at 04:03
  • @Enigmativity: (OP told me that) queues are some sort of list of the transactions to perform. Read the comment above yours. – displayName Sep 24 '15 at 05:19
  • @displayName - I kind of figured that. I'm after a description of how that works. Interfaces, classes, methods, etc. – Enigmativity Sep 24 '15 at 07:46

1 Answers1

2

About your current approach

Indeed it is not the right approach. High number of queues read from database will spawn high number of threads which might be bad. You will create a new thread each time. Better to create some threads and then re-use them. And if you want tasks, better to create LongRunning tasks and re-use them.


Suggested Design

I'd suggest the following design:

  1. Reserve only one task to read queues from the database and put those queues in a BlockingCollection;
  2. Now start multiple LongRunning tasks to read a queue each from that BlockingCollection and process that queue;
  3. When a task is done with processing the queue it took from the BlockingCollection, it will then take another queue from that BlockingCollection;
  4. Optimize the number of these processing tasks so as to properly utilize the cores of your CPU. Usually since DB interactions are slow, you can create tasks 3 times more than the number of cores however YMMV.

Deadlock possibility

They will at least not happen at the application side. However, since the queues are of database transactions, the deadlock may happen at the database end. You may have to write some logic to make your task start a transaction again if the database rolled it back because of deadlock.


Sample Code

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);
}
displayName
  • 13,888
  • 8
  • 60
  • 75
  • Thank you for your response. I need to be able to run the queues in parallel, not one after another. Is there a way to use the ParallelOptions.MaxDegreeOfParallelism to determine how many Tasks I can run at once? – ptn77 Sep 24 '15 at 03:24
  • The number of queues to run in parallel would be based on the number of cores? The service can run on different machines which can be 2, 4, or 8 cores...Can you give an example of the BlockingCollection example where I would remove a queue once it's complete processing...thanks – ptn77 Sep 24 '15 at 03:31
  • @ptn77: You can get the ProcessorCount value for the system and then run x times the number of Tasks. You already have `MyQueue` defined. Skin it to only store the queue or whatever else is essential. Then just create the BlockingCollection. – displayName Sep 24 '15 at 03:35
  • @ptn77: ProcessorCount: https://msdn.microsoft.com/en-us/library/system.environment.processorcount(v=vs.110).aspx BlockingCollection: https://msdn.microsoft.com/en-us/library/dd267312(v=vs.110).aspx – displayName Sep 24 '15 at 03:37
  • Thank you for the references. Why should I use the blockingCollection instead of the ConcurrentDictionary? I needed a way to retrieve the Queue based on the key(queueID) to remove it from the collection once the queue completes... – ptn77 Sep 24 '15 at 03:46
  • I do need the QueueId. From my code sample, should I just replace the section where I'm creating new Threads with Tasks and pass in the parallelOptions with consideration of the System environment processor count? Also, what's the best way to determine the task completion without blocking the parent thread with a wait? My current code has a callback to the ThreadFinish method of the parent thread. Is this recommended? – ptn77 Sep 24 '15 at 04:04
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/90491/discussion-between-displayname-and-ptn77). – displayName Sep 24 '15 at 04:05
  • I needed the QueueId because there is an external application that can stop any of the queues at any given time. Within the Queue class, during the Process() task, there are various loops and specific checks on a stop flag to exit the loops. The parent thread has a method that allows for passing in a QueueId to stop the queue task... – ptn77 Sep 24 '15 at 15:02
  • @ptn77: In that case, you can maintain another `stopList` which will store all the ids that need to be stopped. Now, both the reader and the processors will read the queueID and if that id is present in the stopList, they won't add to the BCollection or won't process if it got added earlier to the BCollection. – displayName Sep 24 '15 at 15:17