12

I need to implement a sort of task buffer. Basic requirements are:

  • Process tasks in a single background thread
  • Receive tasks from multiple threads
  • Process ALL received tasks i.e. make sure buffer is drained of buffered tasks after a stop signal is received
  • Order of tasks received per thread must be maintained

I was thinking of implementing it using a Queue like below. Would appreciate feedback on the implementation. Are there any other brighter ideas to implement such a thing?

public class TestBuffer
{
    private readonly object queueLock = new object();
    private Queue<Task> queue = new Queue<Task>();
    private bool running = false;

    public TestBuffer()
    {
    }

    public void start()
    {
        Thread t = new Thread(new ThreadStart(run));
        t.Start();
    }

    private void run()
    {
        running = true;

        bool run = true;
        while(run)
        {
            Task task = null;
            // Lock queue before doing anything
            lock (queueLock)
            {
                // If the queue is currently empty and it is still running
                // we need to wait until we're told something changed
                if (queue.Count == 0 && running)
                {
                    Monitor.Wait(queueLock);
                }

                // Check there is something in the queue
                // Note - there might not be anything in the queue if we were waiting for something to change and the queue was stopped
                if (queue.Count > 0)
                {
                    task = queue.Dequeue();
                }
            }

            // If something was dequeued, handle it
            if (task != null)
            {
                handle(task);
            }

            // Lock the queue again and check whether we need to run again
            // Note - Make sure we drain the queue even if we are told to stop before it is emtpy
            lock (queueLock)
            {
                run = queue.Count > 0 || running;
            }
        }
    }

    public void enqueue(Task toEnqueue)
    {
        lock (queueLock)
        {
            queue.Enqueue(toEnqueue);
            Monitor.PulseAll(queueLock);
        }
    }

    public void stop()
    {
        lock (queueLock)
        {
            running = false;
            Monitor.PulseAll(queueLock);
        }
    }

    public void handle(Task dequeued)
    {
        dequeued.execute();
    }
}
Servy
  • 202,030
  • 26
  • 332
  • 449
user1300560
  • 255
  • 1
  • 3
  • 12

5 Answers5

8

You can actually handle this with the out-of-the-box BlockingCollection.

It is designed to have 1 or more producers, and 1 or more consumers. In your case, you would have multiple producers and one consumer.

When you receive a stop signal, have that signal handler

  • Signal producer threads to stop
  • Call CompleteAdding on the BlockingCollection instance

The consumer thread will continue to run until all queued items are removed and processed, then it will encounter the condition that the BlockingCollection is complete. When the thread encounters that condition, it just exits.

Eric J.
  • 147,927
  • 63
  • 340
  • 553
  • Thanks for the suggestion. Unfortunately I am limited to .Net 3.5, so that rules out BlockingCollection. Sorry I should have mentioned that in the requirements ;) – user1300560 Sep 12 '12 at 08:10
7

You should think about ConcurrentQueue, which is FIFO, in fact. If not suitable, try some of its relatives in Thread-Safe Collections. By using these you can avoid some risks.

Qerts
  • 935
  • 1
  • 15
  • 29
1

I suggest you take a look at TPL DataFlow. BufferBlock is what you're looking for, but it offers so much more.

spender
  • 117,338
  • 33
  • 229
  • 351
  • Thanks for the suggestion. Unfortunately I am limited to .Net 3.5, so that rules these out. Sorry I should have mentioned that in the requirements ;) – user1300560 Sep 12 '12 at 08:10
1

Look at my lightweight implementation of threadsafe FIFO queue, its a non-blocking synchronisation tool that uses threadpool - better than create own threads in most cases, and than using blocking sync tools as locks and mutexes. https://github.com/Gentlee/SerialQueue

Usage:

var queue = new SerialQueue();
var result = await queue.Enqueue(() => /* code to synchronize */);
Alexander Danilov
  • 3,038
  • 1
  • 30
  • 35
  • According to the [guidelines](https://learn.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/task-based-asynchronous-pattern-tap#naming-parameters-and-return-types) the `Enqueue` method should be named `EnqueueAsync`. – Theodor Zoulias Feb 12 '20 at 12:47
  • @TheodorZoulias why Task.Run and TaskFactory.StartNew don't have async then? Same with Task.ContinueWith. Should be RunAsync, StartNewAsync, ContinueWithAsync. – Alexander Danilov Feb 12 '20 at 13:17
  • @TheodorZoulias moreover, actual enqueue happens synchronously, so after method finishes action is already enqueued. – Alexander Danilov Feb 12 '20 at 13:28
  • If `Enqueue` runs synchronously, then returning a `Task` makes no sense. What this `Task` is supposed to represent? The asynchronous completion of what? – Theodor Zoulias Feb 12 '20 at 13:58
  • Enqueue to the task queue is one thing, task completion is another. Enqueue is synchronous, but completion is not. You can await completion if you want. Same as TaskFactory.StartNew - task creation and starting is synchronous, but completion is not. And it also returns Task. – Alexander Danilov Feb 13 '20 at 15:55
  • Your claim that `TaskFactory.StartNew` is synchronous sounds outrageous to me. This method returns a `Task`, so it can only be synchronous if the returned task is completed. Could you also give an example of an asynchronous method, so that I can understand what is the meaning of the word "asynchronous" for you? – Theodor Zoulias Feb 13 '20 at 16:10
  • Two absolutely equal methods that return Tasks: getContactsListAsync, startContactsListRequest. First is async, second is sync, because of different naming. In both methods request is sent immediately (synchronously), but completion is awaited asynchronously. – Alexander Danilov Feb 13 '20 at 16:17
  • These methods are not provided by the .NET Framework. Could you give an example of a built-in asynchronous method? Or there aren't any? – Theodor Zoulias Feb 13 '20 at 16:22
  • What about the built-in method [`HttpClient.SendAsync`](https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient.sendasync). Is it synchronous or asynchronous, and why? – Theodor Zoulias Feb 13 '20 at 16:33
  • Try to answer my questions about TaskFactory etc first pls. – Alexander Danilov Feb 14 '20 at 14:27
  • Alexander you have asked no questions, and you are avoiding to answer my questions. You have made some egregious claims that `TaskFactory.StartNew` is not asynchronous, and that asynchrony has something to do with the naming of a method. If you don't feel comfortable about clarifying your ideas, then your ideas stand no chance of being propagated. Simple. – Theodor Zoulias Feb 14 '20 at 14:51
  • @TheodorZoulias question in my 1 comment, no answer and you talk about avoiding answers. I've told enough about my ideas, and seems that you've heard them right. This is my last answer to you. – Alexander Danilov Feb 17 '20 at 14:37
  • Your question is why Microsoft didn't follow its own guidelines when naming the method `Task.ContinueWith`. I don't know the answer to that. If you want an official answer ask Microsoft, not me. I didn't oppose your judgement to ignore the guidelines because of that. Instead I asked you to explain **your** naming patterns, and **your** definition of asynchrony. If you have developed a robust and coherent system/theory I could become a follower. – Theodor Zoulias Mar 05 '20 at 23:28
0

You could use Rx on .NET 3.5 for this. It might have never come out of RC, but I believe it is stable* and in use by many production systems. If you don't need Subject you might find primitives (like concurrent collections) for .NET 3.5 you can use that didn't ship with the .NET Framework until 4.0.

Alternative to Rx (Reactive Extensions) for .net 3.5

* - Nit picker's corner: Except for maybe advanced time windowing, which is out of scope, but buffers (by count and time), ordering, and schedulers are all stable.

Community
  • 1
  • 1
yzorg
  • 4,224
  • 3
  • 39
  • 57