TeroK
TeroK

Reputation: 103

How to check .NET BlockingCollection status and wait for completion?

I have a C# worker thread that saves batch of camera bitmaps to disc in by using BlockingCollection. It works nicely but I need a method to be called from main app that blocks execution until all queued bitmaps are saved (see end of message for example).

The whole class looks like:

namespace GrabGUI
{
    struct SaveTask
    {
        public string fname;
        public Bitmap bm;
    }

    class ImageWriter
    {
        private BlockingCollection<SaveTask> queue = new BlockingCollection<SaveTask>();

        //resets when read
        public string ErrorsOccurred;
        private Thread writerthread;

        public ImageWriter()
        {
            writerthread = new Thread(new ThreadStart(Writer));
            writerthread.Start();
        }


        public void Stop()
        {
            queue.CompleteAdding();
        }

        public string WaitForIdleAndGetErrors()
        {
            //HOW TO WAIT FOR QUEUE TO GET PROCESSED?

            return ErrorsOccurred;
        }

        public void AddImageToQueue(string filename, Bitmap bmap)
        {
            SaveTask t;
            t.bm=bmap;
            t.fname=filename;
            queue.Add(t);
        }

        void Writer()
        {
            while (queue.IsCompleted==false)
            {
                try
                {
                    SaveTask t = queue.Take();// blocks when the queue is empty
                    SaveBitmap(t.fname, t.bm);
                }
                catch (Exception e)
                {
                    //comes here after called Stop
                    return;
                }
            }
        }


        private void SaveBitmap(string filename,Bitmap m_bitmap)
        {
            //saving code
        }

    }
}

And is used from main app like:

ImageWriter w=new ImageWriter();

w.AddImageToQueue(fname,bitmap);//repeat many times
...

//wait until whole queue is completed and get possible errors that occurred
string errors=w.WaitForIdleAndGetErrors();

So the question is how to implement the blocking wait to WaitForIdleAndGetErrors(). Any suggestions?

Upvotes: 4

Views: 4048

Answers (3)

SteelSoul
SteelSoul

Reputation: 141

To add to the previous answers, if you want to do it in a non-blocking manner (Thread.Join is blocking) or want to give more control to the caller, you can add the following to the ManualResetEventSlim example:

    ManualResetEventSlim _mre = new ManualResetEventSlim(false);

    public Task<string> WaitForIdleAndGetErrors()
        {
            return Task.Factory.StartNew(() =>
            {
                if (!_queue.IsCompleted)
                {
                    _mre.Wait();
                }
                return ErrorsOccurred;
            });
        }

Then, in addition to having your UI (assuming)Thread free, you also control whether you want to block on the call, when to block on the task and for how long. Possible call could be:

// Calling Thread
...
imageWriter.WaitForIdleAndGetErrors.Wait(myDesiredWaitLimit);
...

Upvotes: 0

Jim Mischel
Jim Mischel

Reputation: 134005

The thread will exit when the queue is empty. So your WaitForIdleAndGetErrors method just has to wait for the thread to end. That's what Thread.Join does:

    public string WaitForIdleAndGetErrors()
    {
        // Wait for thread to exit
        writerthread.Join();

        return ErrorsOccurred;
    }

Thread.Join does a non-busy wait. You won't be burning CPU while waiting for the thread to exit, and there's no need for a separate event.

By the way, you can simplify your thread by taking advantage of BlockingCollection.GetConsumingEnumerable:

    void Writer()
    {
        foreach (SaveTask t in queue.GetConsumingEnumerable())
        {
            try
            {
                SaveBitmap(t.fname, t.bm);
            }
            catch (Exception e)
            {
                //comes here after called Stop
                return;
            }
        }
    }

Upvotes: 4

T McKeown
T McKeown

Reputation: 12857

One very simple way here:

public string WaitForIdleAndGetErrors()
{
    while (queue.IsCompleted == false )
    {
       System.Threading.Thread.Current.Sleep(100);
    }

   return ErrorsOccurred;
}

Or use a ManualResetEventSlim:

Declare new instance var:

ManualResetEventSlim _mre = new ManualResetEventSlim(false);

public string WaitForIdleAndGetErrors()
{
    if (queue.IsCompleted == false )
    {
       _mre.Wait();
    }

   return ErrorsOccurred;
}

Then when your queue is complete signal the mre.

_mre.Set();   // this will release any thread waiting.

Finally, you need to Reset() the _mre when an item is added for processing, this will cause any Wait() to block until the _mre is signaled (via Set())

Things to consider

If you call this with the UI Thread then all UI interaction will appear to be frozen, you'd be better off using a Timer to poll or something similar otherwise you will have a bad UI experience.

However you could fire this whole thing off using a BackgroundWorker and then Invoke a method/event that the UI thread will process upon completion.

Upvotes: 4

Related Questions