2

For now or since I learn to program, I'm using following codes to do this:

  ConcurrentQueue<MyPkg> messageQueue= new ConcurrentQueue<MyPkg>;
  private void Post()
        {
            while (true)
            {
                if (messageQueue.IsEmpty)
                {
                    //Post to server
                }
                Thread.Sleep(1);
            }
        }

This while-sleep "Query Mode" looks so stupid....Most time the cpu just keep context switching for nothing.But many people are doing this. Is there a better way? A "interrupt" or "trigger" like way?

joe
  • 1,078
  • 2
  • 11
  • 30
  • 1
    A solution (that might be too heavy) may be [`BlockingCollection`](https://learn.microsoft.com/en-us/dotnet/standard/collections/thread-safe/blockingcollection-overview). – Patrick Hofman Nov 29 '17 at 08:48
  • 1
    Aside from anything else, unless it's a type designed for thread-safe access, this may well be broken already. – Jon Skeet Nov 29 '17 at 08:48
  • And then a [blocking queue](https://stackoverflow.com/q/530211/993547). – Patrick Hofman Nov 29 '17 at 08:49
  • Nothing in your code is going to have much impact on how much the cpu needs to context switch - and it's not for nothing, there's always other processes wanting CPU time. Having said that, generally there *are better ways to have one thread wait until another thread (or process) signals it when, for instance, there's data to process. Do have you control over what happens when data is put in the queue? – Dylan Nicholson Nov 29 '17 at 08:52
  • If you have 2 threads - One being the writer thread which puts data in messageQueue and the Other being Reader thread (whose code you posted) - I would recommend using one of the Thread Sync techniques (Auto/Manual Reset Event). Reader should block wait (WaitOne) when messageQueue has no item and Writer should trigger (Set) when new data item is wriiten. – Prateek Shrivastava Nov 29 '17 at 08:56
  • ConcurrentQueue in C# @RenéVogt – joe Nov 29 '17 at 08:59
  • I used timer tick to pull message from ConcurrentQueue, which is near to while(true) but looks more healthy. – DSA Nov 29 '17 at 09:02
  • @DylanNicholson I do, they come frome a WCF namedpipe, and I enqueue them myself. – joe Nov 29 '17 at 09:03
  • Then I'd say Pratreek you should put your comment as an answer. – Dylan Nicholson Nov 29 '17 at 09:07
  • 2
    Just wrap your concurrentqueue into BlockingCollection as suggested above, why reinvent the wheel? – Evk Nov 29 '17 at 09:11

1 Answers1

2

There are many different ways to solve your problem. Which one is best depends on your situation. It is impossible to provide all the possible options here, especially since some of the solutions can become quite complex. At the end it is about knowing your options and using them as building blocks in your solution.

Here are some things you could do. Note that these are very basic implemenations that do not take into account cancellation and timeouts. You would have to add them to the mix depending on your requirements.

Spin wait

You can use SpinWait to wait until your queue has any items. SpinWait is a good choice when you expect the wait times to be rather short, because in these cases it would spin instead of causing a thread context switch. SpinWait automatically locks if the waiting time gets too long, but still you shouldn't use it for longer waits.

An example would be:

while (true)
{
    while (!messageQueue.IsEmpty)
    {
        //Post to server
    }
    SpinWait.SpinUntil(() => !messageQueue.IsEmpty);
}

Wait handle

Another option is to use a wait handle that can be set when an item is enqueued and that blocks the receiving thread. This will usually cause a thread switch, so this is a good option when data is coming in not too frequently, but processing is quite expensive or other threads might make better use of the processing time while there is nothing in the queue.

A good option for a wait handle would be an AutoResetEvent:

AutoResetEvent waitHandle = new AutoResetEvent(false);
while (true)
{
    waitHandle.WaitOne();
    while (!messageQueue.IsEmpty)
    {
        //Post to server
    }
}

//When you enqueue your items...

messageQueue.Enqueue(item);
waitHandle.Set();

Starting a task to process the queue

If the data in the queue comes in in short bursts followed by longer breaks, it can be best to not run anything in the background when there is nothing to do and only start a task that will empty the queue when there are items in it. In the longer breaks you can return the thread that will process the queue to the thread pool, where it can be used for something more important.

You can start a Task on demand and use a bool variable to track whether your processing task is running.

bool isProcessing = false;
object processingSync = new object();

//...

void ProcessQueue() 
{
    bool shouldContinue;
    do
    {
        object item; //Set thhis to the right type
        lock (processingSync)
        {
            //Note: you wouldn't actually need a concurrent queue here,
            //since you are locking during enqueue and dequeue
            shouldContinue = messageQueue.TryDequeue(out item);
            if (!shouldContinue)
            {
                isProcessing = false;
            }
        }
        //Process item
    }
    while (shouldContinue);
}

//When you enqueue your items...

lock (processingSync)
{
    messageQueue.Enqueue(item);
    if (!isProcessing)
    {
        Task.Run(ProcessQueue);
        isProcessing = true;
    }
}

When you know in advance how many items are in the queue, you could also use a BlockingCollection, which has the advantage that you don't have to do your own manual locking.

Sefe
  • 13,731
  • 5
  • 42
  • 55