Пожалуйста, помогите мне сделать этот поток кода безопасным

2

У меня проблема с загрузкой и фильтрацией потоков данных.

Следующий код моего базового класса управления, который обрабатывает всю совокупность данных через BackgroundWorker. Это приводит к ошибке "this.DataWorker.RunWorkerAsync()", говорящей, что BackgroundWorker занят.

/// <summary>
/// Handles the population of the form data.
/// </summary>
/// <param name="reload">Whether to pull data back from the WebService.</param>
public void Populate(bool reload)
{
    if (!this.DataWorker.IsBusy)
    {

        // Disable the filter options
        IvdSession.Instance.FilterManager.SetEnabledState(this.GetType(), false);

        // Perform the population
        this.DataWorker.RunWorkerAsync(reload);

    }
    else if (!reload)
    {
        // If the data worker is busy and this is a not reload, then something bad has happened (i.e. the filter has run during a reload.)
        throw new InvalidOperationException("The DataWorker was busy whilst asked to reload.");
    }
}

Код вызывается в двух возможных местах. Сначала таймер на форме, в которой находится элемент управления:

private void tmrAutoRefresh_Tick(object sender, EventArgs e)
{
    if (!(this.CurrentBody == null))
    {
        this.CurrentBody.Populate(true);
    }
}

И во-вторых, каждый раз, когда пользователь выбирает параметр фильтра из нескольких выпадающих списков:

public void Filter()
{
    if (!m_BlockFilter)
    {
        IvdInstance.Main.CurrentBody.FirstRun = true;
        IvdInstance.Main.CurrentBody.Populate(false);
    }
}

Таймер в основной форме запускается каждые 60 секунд и передается true для метода Population. Передача reload как истины сообщает BackgroundWorker, что ему нужно вытащить свежий набор данных из WebService:

void dataWorker_DoWork(object sender, DoWorkEventArgs e)
{

    try
    {

        if (base.FirstRun)
        {
            base.CleanListView();
        }

        if ((bool)e.Argument)
        {
            byte[] serialized = IvdSession.DataAccess.GetServiceCalls(IvdSession.Instance.Company.Description, IvdSession.Instance.Company.Password, null);
            m_DataCollection = new DalCollection<ServiceCallEntity>(serialized);
        }

        List<ServiceCallEntity> collection = this.ApplyFilter();
        base.HandlePopulation<ServiceCallEntity>(collection, e);

    }
    catch (WebException ex)
    {
        // Ignore - Thrown when user clicks cancel
    }
    catch (System.Web.Services.Protocols.SoapException ex)
    {
        // Log error on server and stay transparent to user
        base.LogError(ex);
    }
    catch (System.Data.SqlClient.SqlException ex)
    {
        // Inform user that the database is unavailable
        base.HandleSystemUnavailable(ex);
    }

}

Насколько мне известно, ошибка возникает, когда мне удается щелкнуть параметр фильтра точно в то же время, когда таймер запускает событие популяции. Я полагаю, что есть что-то отсутствует в методе Populate, то есть в блокировке, но я не уверен, как правильно его использовать в этом случае.

Код предпочтительнее ввода пользователя. Если пользователь выбирает параметр фильтра, автоматическое обновление должно быть заблокировано, если происходит автоматическое обновление, параметры фильтра временно отключены. Если они срабатывают одновременно, пользовательский ввод должен получить приоритет (если возможно).

Надеюсь, кто-то может помочь!

Теги:
multithreading
winforms
thread-safety
backgroundworker

2 ответа

2
Лучший ответ

Сначала добавьте блокировку вокруг тела метода Populate:

private object _exclusiveAccessLock = new object();
public void Populate(bool reload)
{
    lock (_exclusiveAccessLock)
    {
         // start the job
    }
}

Это поможет вам избежать состояния гонки (хотя): если я правильно понял, так как вы используете таймер Windows.Forms, он всегда срабатывает из потока Gui, поэтому они никогда не должны выполняться точно в одно и то же время).

Затем я не уверен, что вы вообще должны исключить исключение. Например, вы можете установить дополнительный флаг, который показывает, что рабочий еще не закончил, но это то, что IsBusy должно сказать вам все равно.

Тогда есть флаг m_BlockFilter. Я не вижу, от чего вы его настраиваете. Он также должен быть установлен внутри замка, а не в фоновом потоке, потому что в этом случае вы не можете быть уверены, что он не будет задерживаться. Вам также нужно сделать поле volatile, если вы собираетесь использовать его как флаг кросс-потока.

  • 0
    @ Гроо, не знаю точных спецификаций Windows, но с многоядерными процессами вы не сможете запустить две вещи одновременно?
  • 0
    Да, метод должен быть поточно-ориентированным. Но есть правило, что вы всегда должны обновлять элементы Gui из потока Gui. Поэтому Windows.Forms.Timer позаботится о том, чтобы добавить обработчик событий в очередь потоков Gui (он вызывает его из потока Gui), чтобы упростить вещи.
Показать ещё 2 комментария
1

См. Синхронизация потоков (Руководство по программированию на С#):

public class TestThreading
{
    private System.Object lockThis = new System.Object();

    public void Function()
    {

        lock (lockThis)
        {
            // Access thread-sensitive resources.
        }
    }
}

Изменить. Вы не хотите, чтобы два потока вошли в Populate, чтобы вы могли сделать что-то, как показано ниже:

public void Populate(bool reload)
{

    lock (lockThis)
    {
        // Disable the filter options
        IvdSession.Instance.FilterManager.SetEnabledState(this.GetType(), false);

        // do actual work.
    }

}

Edit2. У вас получилось хорошо работать с BackgroundWorker, так что, возможно, вы могли бы сделать что-то подобное, чтобы оживить другой поток.

public void Populate(bool reload)
{
    while (this.DataWorker.IsBusy) {
        Thread.Sleep(100);
    }

    // Disable the filter options
    IvdSession.Instance.FilterManager.SetEnabledState(this.GetType(), false);

    // Perform the population
    this.DataWorker.RunWorkerAsync(reload);
}
  • 0
    В контексте приведенного выше кода, где бы я это использовал?

Ещё вопросы

Сообщество Overcoder
Наверх
Меню