Почему всем элементам в моей очереди задач присваивается одинаковое значение? - PullRequest
2 голосов
/ 14 декабря 2011

Я пытался сделать должную осмотрительность, прежде чем задавать этот вопрос, но я не могу найти то, что я ищу. Я считаю, что столкнулся с проблемой производитель-потребитель . Я пишу приложение winforms в C #, которое использует два потока: один для пользовательского интерфейса и один для фонового работника. Элементы добавляются в очередь задач через обработчик событий кнопки отправки (т. Е. Всякий раз, когда пользователь нажимает кнопку «Отправить»). Если в очереди уже ничего нет, то вызывается фоновый работник и начинает обрабатывать очередь. Если фоновый работник занят, то задача просто добавляется в очередь. Теоретически, фоновый работник перейдет к следующему элементу в очереди, когда закончит свою текущую работу.

В моем потоке пользовательского интерфейса у меня есть следующий код, который создает экземпляр объекта DiscQueue, а затем добавляет элементы в его очередь:

private DiscQueue discQueue = new DiscQueue();
this.discQueue.AddToQueue(currentCD);

Ниже мой класс DiscQueue. Моя функция AddToQueue добавляет диск в очередь и затем вызывает RunWorkerAsync (), если bw еще не занят. Затем в 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();
        }
    }
}

В ходе тестирования я обнаружил, что быстрое последовательное добавление элементов в очередь каким-то образом забивает очередь, так что всем элементам в очереди присваивается значение (возможно, ссылка на?) Последнего элемента, добавленного в очередь. Когда я проходил по коду в отладке, похоже, что это происходит к тому времени, когда вы переходите к if (! This.bw.IsBusy) в функции AddToQueue. Я не уверен, что происходит.

Помимо ответа на мой конкретный вопрос, я уверен, что я делаю басы, и я был бы очень рад узнать "правильный способ" сделать это.

РЕДАКТИРОВАТЬ: Вот мой класс диска:

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 Ответ

3 голосов
/ 14 декабря 2011

Стандарт .Net Queue<T> не является "потокобезопасным";упор мой:

A 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);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...