tags:

views:

168

answers:

4

Hey, I'm trying to implement a ConcurrentQueue in C# for an asynchronous server. Items are being queued as soon as a complete message is received. To dequeue messages, I'm making a small number of threads to do the work of dequeueing and servicing the requests. This is inadequate as each thread is using a while loop, which consumes rather a large amount of processor time, for obvious reasons.

Would anyone know of a method of dequeueing messages when required but not consuming so much processing time.

{
    ...

    for (int i = 0; i < 3; i++)
    {
        Thread t = new Thread(new ThreadStart(startParsingMessages));
        t.Start();
    }

    ...
}

private void startParsingMessages()
{
    QueueContainer dequeued = null;
    Console.WriteLine("Trying");
    while (true)
    {
        if (queue.TryDequeue(out dequeued))
        {
            Console.WriteLine("processing queue");
            ProcessMessage(dequeued.socket, dequeued.message);
        }
    }
}
+3  A: 

Instead of using ConcurrentQueue<T> directly, have you tried wrapping it in a BlockingCollection<T>? Then you can use TryTake(out T, TimeSpan) etc. I believe that's the expected use: that the concurrent collections themselves would usually be there just so you can select how the blocking collection will work.

That doesn't have to be the only use for these collections of course, but particularly for ConcurrentQueue<T>, the producer/consumer queue scenario is the most common one - at which point BlockingCollection<T> is the way to make it easy to do the right thing.

Jon Skeet
+1 You almost imply that the concurrent collections are only there to back `BlockingCollection` and I'm not sure that's true. But you're dead on with using the `BlockingCollection` here to minimize all of the ceremony to go along with traditional producer/consumer patterns.
Marc
@Marc: I wouldn't say it's quite true to that extent, but I think that'll be the *predominant* use. I'll update the answer.
Jon Skeet
Hi, thanks for the suggestions! I've wrapped my ConcurrentQueue in a BlockingCollection and the results are fantastic. I'm running 20 threads concurrently to eat through the queue, with a blocking time of 0.1 seconds per thread via the TryTake(out T, TimeSpan s) method, and it's working a charm. Request are being serviced within an appropriate time (quicker than without the concurrentqueue) and the CPU load has never been lower.Thanks again!
@user352891: Woot! That's great to hear :)
Jon Skeet
+2  A: 

There is a pretty well know pattern in concurrent programming called Parallel Producer/Consumer pattern. This article might help resolve some of your issues. http://blogs.msdn.com/b/csharpfaq/archive/2010/08/12/blocking-collection-and-the-producer-consumer-problem.aspx

btlog
A: 

It is a producer-consumer pattern.

Check this MSDN link.

Al Bundy
+1  A: 

You could use a static locking object for the threads to wait on and then pulse it when something is ready to be processed.

static readonly object queueLock = new object();

// Threads that enqueue an object
void QueueMessage() {
  lock (queueLock) {
    queue.Enqueue(obj);
    Monitor.Pulse(queueLock);
  }
}
// Thread dequeuer
private void startParsingMessages() {
  QueueContainer dequeued = null;
  Console.WriteLine("Trying");
  while (true) {
    lock(queueLock) {
      if (!queue.TryDequeue(out dequeued)) {
        Console.WriteLine("No object to dequeue, waiting...");
        // Threads will wait here and only one will be released when .Pulse()d
        Monitor.Wait(queueLock);
        dequeued = queue.Dequeue();
      }
      if (dequeued != null) {
        Console.WriteLine("processing queue");
        ProcessMessage(dequeued.socket, dequeued.message);
      }
    }
  }
}

Keep in mind that this can get quite complicated with the more branches you have, but the gist of it is, you lock on a common object and call Monitor.Wait to wait on an object to be Pulsed. When this happens only one thread that was waiting on it will be released. If you enqueue a lot of objects and want them all to go, you can call Monitor.PulseAll which will release all threads that were waiting on the object.

Joshua
Hello, thanks for the suggestion! I implemented your suggestion and the results were very promising with a small number of requests. However as soon as there were a large number of requests to be serviced, the time taken to respond increased dramatically. Again, thanks for the suggestion, it's certainly a technique I'll keep in mind for future projects.