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 секунд и переходит к методу Population. Передача перезарядки в истинах говорит 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, то есть в блокировке, но я не уверен, как правильно его использовать в этом случае.

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

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

ответ

2

Во-первых, добавить блокировку вокруг Populate тело метода:

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

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

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

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

+0

@Groo, не знаете точную спецификацию Windows, но с несколькими ядрами, не могли бы вы одновременно запустить две вещи? –

+0

Да, метод * должен * быть потокобезопасным. Но есть правило, что вы всегда должны обновлять элементы Gui из потока Gui. Поэтому Windows.Forms.Timer позаботится о добавлении обработчика событий в очередь потоков Gui (он вызывает его из потока Gui), чтобы упростить ситуацию. – Groo

+0

Эй, кто ты шутишь с «Не знаю точной спецификации Windows»? :) – Groo

1

См Thread Synchronization (C# Programming Guide):

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

    public void Function() 
    { 

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

Edit: Вы не хотите, два потока ввода Заполнить, так что вы могли бы сделать что-то, как ниже:

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

В контексте приведенного выше кода, где бы я его использовал? – GenericTypeTea