在提出这个问题之前,我一直在努力做尽职调查,但我似乎无法找到我正在寻找的东西。我相信我正在与producer-consumer problem对抗。我在C#中编写了一个winforms应用程序,它使用两个线程:一个用于UI,一个用于后台工作者。项目通过提交按钮事件处理程序添加到任务队列(即每当用户点击“提交”时)。如果队列中没有任何内容,则调用后台工作程序并开始处理队列。如果后台工作程序正忙,则只需将任务添加到队列中。从理论上讲,后台工作程序在完成当前工作时将继续执行队列中的下一个项目。
在我的UI线程中,我有以下代码实例化DiscQueue对象,然后将项添加到其队列中:
private DiscQueue discQueue = new DiscQueue();
this.discQueue.AddToQueue(currentCD);
以下是我的DiscQueue课程。我的AddToQueue函数将光盘添加到队列,然后如果bw尚未忙,则调用RunWorkerAsync()。然后,在bw_DoWork中,我从队列中获取一个项目,并在其上完成我需要做的工作。当bw完成其任务时,它应该调用bw_RunWorkerCompleted,如果队列中有更多项目,它应该指示它继续通过队列。
class DiscQueue
{
private Queue<Disc> myDiscQueue = new Queue<Disc>();
private BackgroundWorker bw = new BackgroundWorker();
// Initializer
public DiscQueue()
{
// Get the background worker setup.
this.bw.WorkerReportsProgress = false;
this.bw.WorkerSupportsCancellation = false;
this.bw.DoWork += new DoWorkEventHandler(bw_DoWork);
}
public void AddToQueue(Disc newDisc)
{
this.myDiscQueue.Enqueue(newDisc);
if (!this.bw.IsBusy)
{
this.bw.RunWorkerAsync();
}
}
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
DiscPreparationFactory discToPrepare = new DiscPreparationFactory();
Disc currentDisc = new Disc();
currentDisc = this.myDiscQueue.Dequeue();
discToPrepare.PrepareAndPublish(currentDisc);
}
private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (this.myDiscQueue.Count > 0)
{
this.bw.RunWorkerAsync();
}
}
}
在测试中,我发现快速连续地向队列添加项目会以某种方式破坏队列,以便为队列中的所有项目分配(添加到队列的最后一项)的值(可能是对?的引用)。当我在调试中逐步完成代码时,这似乎发生在AddToQueue函数中的if(!this.bw.IsBusy)时。我不确定发生了什么。
除了回答我的具体问题之外,我确信我正在做一些低调的事情,我很高兴知道这样做的“正确方法”。
编辑:这是我的Disc课程:
public class Disc
{
public enum DiscFormat
{
Audio,
Data,
}
private string sku;
private int quantity;
private DiscFormat format;
public string Sku
{
get
{
return this.sku;
}
set
{
this.sku = value;
}
}
public DiscFormat Format
{
get
{
return this.format;
}
set
{
this.format = value;
}
}
public int Quantity
{
get
{
return this.quantity;
}
set
{
this.quantity = value;
}
}
}
编辑2:我的DiscQueue对象在我的MainForm.cs文件中实例化,如下所示:
public partial class MainForm : Form
{
Disc currentCD = new Disc();
private DiscQueue discQueue = new DiscQueue();
public MainForm()
{
// Do some stuff...
}
// Skipping over a bunch of other methods...
private void buttonSubmit_Click(object sender, EventArgs e)
{
currentCD.Sku = this.textBoxSku.Text;
currentCD.Quantity = (int)this.numericUpDownQuantity.Value;
if (this.radioButtonAudio.Checked)
currentCD.Format = Disc.DiscFormat.Audio;
else
currentCD.Format = Disc.DiscFormat.Data;
this.discQueue.AddToQueue(currentCD);
}
}
答案 0 :(得分:3)
标准.Net Queue<T>
不是“线程安全的”;强调我的:
只要未修改集合,
Queue<T>
可以同时支持多个阅读器。即便如此,通过集合枚举本质上不是一个线程安全的过程。为了在枚举期间保证线程安全,您可以在整个枚举期间锁定集合。 要允许多个线程访问集合以进行读写,您必须实现自己的同步。
如果你有.Net 4.0,你应该考虑改用ConcurrentQueue<T>
。
如果没有,您可以通过简单的锁定保护DiscQueue
内的读/写访问权限:
/// <summary>
/// Synchronizes access to <see cref="DiscQueue.myDiscQueue" />.
/// </summary>
private object queueLock = new object();
然后你的读者会使用锁定:
Disc currentDisc = null;
lock (this.queueLock)
{
// protect instance members of Queue<T>
if (this.myDiscQueue.Count > 0)
{
currentDisc = this.myDiscQueue.Dequeue();
}
}
// work with currentDisk
你的作家会使用锁定:
lock (this.queueLock)
{
this.myDiscQueue.Add(currentCD);
}