c# - 为什么我的任务队列中的所有项目都被分配相同的值?

标签 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) 时,这似乎发生了。我不确定发生了什么事。

除了回答我的具体问题之外,我确信我做的事情很糟糕,而且我很高兴知道执行此操作的“正确方法”。

编辑:这是我的光盘类:

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

最佳答案

标准.Net Queue<T> 不是“线程安全”;强调我的:

A Queue<T> can support multiple readers concurrently, as long as the collection is not modified. Even so, enumerating through a collection is intrinsically not a thread-safe procedure. To guarantee thread safety during enumeration, you can lock the collection during the entire enumeration. To allow the collection to be accessed by multiple threads for reading and writing, you must implement your own synchronization.

如果您有 .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);
}

关于c# - 为什么我的任务队列中的所有项目都被分配相同的值?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8508271/

相关文章:

c# - 提供的 ClaimsIdentity 上不存在类型为 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier' 的声明

c# - 如何诊断 dotnet 进程消耗高 CPU 的情况?

c# - SendKeys.SendWait ("^c") 问题

python - 在 Python 中进行线程处理时出现 AssertionError

c# - IEnumerable 中的求和、平均、连接等项的实现选项

c# - 如何最多显示两位数但可以显示更少?

c# - 创建 Windows 窗体作为类库

c# - SynchronizationContext.Current 在主线程上为空

java - 使用 Enum 的单例与使用双重检查锁定的单例

c - 在 C 语言的 Ubuntu 上,多个线程不能同时工作