为什么我的任务队列中的所有项目都被赋予相同的值?

时间:2011-12-14 16:46:55

标签: c# winforms multithreading backgroundworker task-queue

在提出这个问题之前,我一直在努力做尽职调查,但我似乎无法找到我正在寻找的东西。我相信我正在与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);
    }
}

1 个答案:

答案 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);
}