Фоновая рабочая синхронизация - PullRequest
4 голосов
/ 10 августа 2010

Допустим, у меня есть класс, который должен сгенерировать какой-то идентификатор (например, GUID) для меня.Сейчас, к сожалению, генерация идентификатора - это довольно долгий процесс, и если мне понадобится сотня из них, я столкнусь с проблемой значительного замедления.Чтобы избежать этого, я сохраняю очередь предварительно сгенерированного идентификатора, и когда эта очередь начинает исчерпывать их, я использую BackgroundWorker, чтобы генерировать новые и помещать их в очередь.Но есть некоторые проблемы, с которыми я столкнулся.Самый большой на данный момент - как убедиться, что в случае полного исчерпания очереди по идентификаторам основной поток ожидает, когда BackroundWorker сгенерирует и поместит их в очередь.Вот код, который у меня есть на данный момент.

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();   
    }
}

Очевидно, он не работает правильно.Кажется, у меня есть проблема с правильным временем для вызова методов WaitOne и Set.И иногда свойство IsBusy возвращает значение true, даже если рабочий уже завершил свою работу.


РЕДАКТИРОВАТЬ:

Это WinForm, и мне необходимо использовать .NET 2.0

Ответы [ 5 ]

3 голосов
/ 10 августа 2010

В .NET 4 вы можете использовать BlockingCollection<T> и более обобщенно IProducerConsumerCollection<T>

Вот пример 2 задач, одно добавление, а другое выполнение с его использованием.

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

3 голосов
/ 10 августа 2010

У вас есть проблема классического производителя-потребителя. Взгляните на http://en.wikipedia.org/wiki/Producer-consumer_problem

Простое объяснение состоит в том, что у вас будет две темы. Один будет производителем (генератором GUID), а другой - потребителем.

Вы будете синхронизировать эти потоки с помощью семафоров. Семафор будет ответственен за остановку производителя, когда очередь заполнена, и за остановку потребителя, когда он пуст.

Этот процесс очень хорошо объяснен в статье в Википедии, и я уверен, что вы можете найти базовую реализацию Producer-Consumer в c # в Интернете.

2 голосов
/ 10 августа 2010

Есть некоторые ошибки, связанные с синхронизацией потоков, см. В измененном коде ниже.Когда вы применяете синхронизацию блокировки к очереди, обратите внимание, чтобы поставить под блокировку все варианты использования очереди.Я изменил метод GetId, чтобы проверять наличие новых идентификаторов, если их нет.

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();   
    }
}
1 голос
/ 10 августа 2010

Ваш основной код (предположительно WinForms) вызывает mWaitHandle.WaitOne() в определенный момент.В этот момент Messagepump заблокирован, и Bgw не сможет вызвать событие Completed.Это означает, что флаг IsBusy остается истинным: deadlock.

Подобные проблемы могут возникнуть, если код внутри DoWork выдает исключение.

Edit:

Я думаю, что вы могли бы решить большинствопроблемы с использованием потока ThreadPool для замены Bgw.И простой volatile bool isbusy флаг.

0 голосов
/ 11 августа 2010

ОК, вот окончательное решение, с которым я согласился.Этот не использует BackgroundWorker, но он работает.Спасибо Эду, который указал на проблему производителя-потребителя.Я использовал пример, предоставленный MSDN, расположенный здесь .

...