2010-08-10 109 views
4

可以说我有一个类应该为我生成一些ID(例如GUID)。现在不幸的是,身份证生成是一个有点漫长的过程,如果我需要一百个身份证,那么我会遇到显着减速问题。为了避免这些情况,我保留了一个预生成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

+0

这似乎是一个真实的模拟,这个代码也发生错误吗?它是WinForms还是WPF或...? – 2010-08-10 12:38:02

+1

这是一个WinForm。上面的代码是精简版,但它的工作方式完全相同,并具有相同的错误。 – 2010-08-10 14:42:01

回答

3

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

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

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

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

+0

感谢您的提示。我会研究它。 – 2010-08-10 14:38:51

1

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

如果DoWork内的代码引发异常,则可能会出现类似问题。

编辑:

我认为,你可以通过使用线程池线程更换BGW解决大部分问题。和一个简单的volatile bool isbusy标志。

+0

对,所以主线程需要空闲以供工作人员改变其状态。正如我怀疑... – 2010-08-10 14:36:17

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

好的,指出,谢谢。认为它仍然不起作用... – 2010-08-10 14:32:40

3

在.NET 4中,你可以使用BlockingCollection<T>和更一般IProducerConsumerCollection<T>

这里有2项任务,一个加入另回吐,使用它的例子。

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

+0

听起来像我可以使用的东西...如果我不需要坚持.NET 2.0 – 2010-08-10 14:28:46

0

OK,继承人的最终解决方案,我跟去了。这一个不使用BackgroundWorker,但它工作。感谢指向生产者 - 消费者问题的Edu。我使用MSDN提供的示例位于here