假设我有一个应该为我生成一些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
答案 0 :(得分:3)
您遇到的问题是经典的生产者 - 消费者问题。看看http://en.wikipedia.org/wiki/Producer-consumer_problem
一个简单的解释是你将有两个线程。一个是生产者(GUID生成者),另一个是消费者。
您将通过使用信号量使这些线程保持同步。信号量将负责在队列满时停止生产者并在消费者为空时停止消费者。
这个过程在维基百科的文章中得到了很好的解释,我打赌你可以在互联网上的c#中找到生产者 - 消费者的基本实现。
答案 1 :(得分:3)
在.NET 4中,您可以使用BlockingCollection<T>
和更通用的IProducerConsumerCollection<T>
以下是2个任务的示例,一个是添加,另一个是使用它。
答案 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提供的示例。