Background Sync

Suppose I have a class that should generate some ID for me (for example, a GUID). Now, unfortunately, generating IDs is a somewhat long process, and if I need a hundred of them, I am faced with the problem of significant slowdown. To avoid this, I save the queue of the pre-generated identifier, and when this queue starts working on them, I use BackgroundWorker to create new ones and place them in the queue. But there are some problems that I have encountered. The biggest at the moment is how to make sure that if the queue ends on identifiers, the main thread expects BackroundWorker to generate and put them in the queue. Here is the code that I have at the moment.

public class IdGenerator { private Queue<string> mIds = new Queue<string>(); private BackgroundWorker mWorker = new BackgroundWorker(); private static EventWaitHandle mWaitHandle = new AutoResetEvent(false); public IdGenerator() { GenerateIds(); this.mWorker.DoWork += new DoWorkEventHandler(FillQueueWithIds); } private void GenerateIds() { List<string> ids = new List<string>(); for (int i = 0; i < 100; i++ ) { ids.Add(Guid.NewGuid().ToString()); } lock (this.mIds) { foreach (string id in ids) { this.mIds.Enqueue(id); } } } public string GetId() { string id = string.Empty; lock (this.mIds) { if (this.mIds.Count > 0) { id = this.mIds.Dequeue(); } if (this.mIds.Count < 100) { if (!this.mWorker.IsBusy) { this.mWorker.RunWorkerAsync(); } } } if (this.mIds.Count < 1) { mWaitHandle.WaitOne(); } return id; } void FillQueueWithIds(object sender, DoWorkEventArgs e) { GenerateIds(); mWaitHandle.Set(); } } 

Obviously, this is not working properly. It seems that I have a problem with the correct time to call the WaitOne and Set methods. And sometimes the IsBusy property returns true, although the worker has already completed his work.


EDIT:

Its WinForm and I have to use .NET 2.0

+4
source share
5 answers

The problem you have is the classic Producer-Consumer problem. Take a look at http://en.wikipedia.org/wiki/Producer-consumer_problem

A simple explanation is that you will have two threads. One of them will be a producer (GUID generator), and the other will be a consumer.

You will synchronize these threads with semaphores. The semaphore will be responsible to stop the producer when the line is full and stop the consumer when it is empty.

The process is very well explained in a Wikipedia article, and I'm sure you can find the basic implementation of Producer-Consumer in C # on the Internet.

+3
source

In .NET 4, you can use BlockingCollection<T> and the more general IProducerConsumerCollection<T>

Here is an example of two tasks, one incremental and the other using it.

http://msdn.microsoft.com/en-us/library/dd997306.aspx

+3
source

There are some errors related to thread synchronization, see the modified code below. When you apply synchronization synchronization to a queue, pay attention to block all use of the queue. I changed the GetId method to search for new identifiers, if there are none.

 public class IdGenerator { private Queue<string> mIds = new Queue<string>(); private BackgroundWorker mWorker = new BackgroundWorker(); private static EventWaitHandle mWaitHandle = new AutoResetEvent(false); public IdGenerator() { GenerateIds(); this.mWorker.DoWork += new DoWorkEventHandler(FillQueueWithIds); } private void GenerateIds() { List<string> ids = new List<string>(); for (int i = 0; i < 100; i++ ) { ids.Add(Guid.NewGuid().ToString()); } lock (this.mIds) { foreach (string id in ids) { this.mIds.Enqueue(id); } } } public string GetId() { string id = string.Empty; //Indicates if we need to wait bool needWait = false; do { lock (this.mIds) { if (this.mIds.Count > 0) { id = this.mIds.Dequeue(); return id; } if (this.mIds.Count < 100 && this.mIds.Count > 0) { if (!this.mWorker.IsBusy) { this.mWorker.RunWorkerAsync(); } } else { needWait = true; } } if (needWait) { mWaitHandle.WaitOne(); needWait = false; } } while(true); return id; } void FillQueueWithIds(object sender, DoWorkEventArgs e) { GenerateIds(); mWaitHandle.Set(); } } 
+2
source

Your main code (presumably WinForms) calls mWaitHandle.WaitOne() at a specific point. At this point, Messagepump is blocked and Bgw will not be able to raise the Completed event. This means that the IsBusy flag remains true: deadlock.

Similar problems can occur if the code inside DoWork throws an exception.

Edit:

I would think that you can solve most problems using ThreadPool thread to replace Bgw. And a simple volatile bool isbusy .

+1
source

OK, here is the final decision I went with. This one does not use BackgroundWorker, but it works. Thanks to Ed, who pointed out the Producer-Consumer problem. I used the example provided by MSDN located here .

0
source

All Articles