Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Background worker synchronization

Lets say I have a class that is supposed to generate some ID (for example GUID) for me. Now unfortunately the ID generation is a somewhat long process and if I need a hundred of those I run into a problem of significant slowdowns. In order to avoid those, I keep a queue of pre-generated ID, and when this queue starts to run down on them I use the BackgroundWorker to generate new ones and place them in the queue. But there are some problems I've run into. The biggest one at the moment is how to make sure that in case the queue compleatelly runs out on IDs the main thread waits for the BackroundWorker to generate and place them in the queue. Heres 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 it doesn't work correctly. It seems that I have a problem with proper timing for calling WaitOne and Set methods. And sometimes the IsBusy property returns true even though the worker has already completed his work.


EDIT:

Its a WinForm and I'm required to use .NET 2.0

like image 440
L.E.O Avatar asked Dec 28 '22 08:12

L.E.O


2 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 will be the producer (the GUID generator) and the other will be the consumer.

You will keep these threads in synch through the use of semaphores. The semaphore will be the responsible to stop the producer when the queue is full and to stop the consumer when it is empty.

The process is all very well explained at the Wikipedia article and I bet you can find a basic implementation of Producer-Consumer in c# on the internet.

like image 140
Edu Avatar answered Jan 13 '23 01:01

Edu


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

Here's an example of 2 tasks, one adding and the other taking, using it.

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

like image 26
James Manning Avatar answered Jan 13 '23 00:01

James Manning