Reputation: 103
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
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
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
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()
)
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