背景工人同步

时间:2010-08-10 12:21:23

标签: c# multithreading backgroundworker

假设我有一个应该为我生成一些ID(例如GUID)的类。现在不幸的是ID生成是一个有点长的过程,如果我需要一百个,我遇到了一个显着减速的问题。为了避免这些,我保留了一个预先生成的ID队列,当这个队列开始向下运行时,我使用BackgroundWorker生成新的队列并将它们放入队列中。但是我遇到了一些问题。目前最大的一个是如何确保在队列完全用完ID的情况下,主线程等待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 个答案:

答案 0 :(得分:3)

您遇到的问题是经典的生产者 - 消费者问题。看看http://en.wikipedia.org/wiki/Producer-consumer_problem

一个简单的解释是你将有两个线程。一个是生产者(GUID生成者),另一个是消费者。

您将通过使用信号量使这些线程保持同步。信号量将负责在队列满时停止生产者并在消费者为空时停止消费者。

这个过程在维基百科的文章中得到了很好的解释,我打赌你可以在互联网上的c#中找到生产者 - 消费者的基本实现。

答案 1 :(得分:3)

在.NET 4中,您可以使用BlockingCollection<T>和更通用的IProducerConsumerCollection<T>

以下是2个任务的示例,一个是添加,另一个是使用它。

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

答案 2 :(得分:2)

有一些与线程同步有关的错误,请参阅下面的更改代码。 当你将锁同步应用到队列时要注意把所有使用队列的锁定。 我已经改变了GetId方法来探测新的id,如果没有的话。

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

答案 3 :(得分:1)

您的主要代码(可能是WinForms)会在某个时刻调用mWaitHandle.WaitOne()。此时Messagepump被阻止,Bgw将无法调用其Completed事件。这意味着IsBusy标志仍然是真的:死锁。

如果DoWork中的代码抛出异常,则会出现类似的问题。

编辑:

我认为你可以通过使用ThreadPool线程替换Bgw来解决大多数问题。还有一个简单的volatile bool isbusy标志。

答案 4 :(得分:0)

好的,这是我最后的解决方案。这个不使用BackgroundWorker,但它可以工作。感谢Edu指出了生产者 - 消费者问题。我使用了位于here的MSDN提供的示例。