【问题标题】:Background worker synchronization后台工作同步
【发布时间】:2010-08-11 18:19:42
【问题描述】:

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

【问题讨论】:

  • 这似乎是真实事物的模型,此代码是否也会发生错误?是 WinForms 还是 WPF 还是...?
  • 这是一个 WinForm。上面的代码是精简版,但它的工作方式完全相同,并且有相同的错误。

标签: c# multithreading backgroundworker


【解决方案1】:

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

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

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

该过程在 Wikipedia 文章中得到了很好的解释,我敢打赌,您可以在 Internet 上找到 C# 中 Producer-Consumer 的基本实现。

【讨论】:

  • 感谢您的提示。我会调查的。
【解决方案2】:

在 .NET 4 中,您可以使用 BlockingCollection&lt;T&gt; 和更通用的 IProducerConsumerCollection&lt;T&gt;

这是一个使用它的 2 个任务的示例,一个添加,另一个获取。

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

【讨论】:

  • 听起来像是我可以使用的东西......如果我不需要坚持使用 .NET 2.0
【解决方案3】:

有一些与线程同步相关的错误,请参阅下面的更改代码。 当您将锁定同步应用于队列时,请注意将队列的所有用途置于锁定状态。 如果没有,我已经更改了 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();   
    }
}

【讨论】:

    【解决方案4】:

    您的主代码(可能是 WinForms)在某个时间点调用 mWaitHandle.WaitOne()。此时 Messagepump 被阻塞,Bgw 将无法调用其 Completed 事件。这意味着 IsBusy 标志保持为真:死锁。

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

    编辑:

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

    【讨论】:

    • 对,所以主线程需要空闲,worker才能改变状态。正如我所怀疑的......
    【解决方案5】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-07-03
      • 2020-08-07
      相关资源
      最近更新 更多