У меня проблема с загрузкой и фильтрацией потоков данных.
Следующий код моего базового класса управления, который обрабатывает всю совокупность данных через 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, то есть в блокировке, но я не уверен, как правильно его использовать в этом случае.
Код предпочтительнее ввода пользователя. Если пользователь выбирает параметр фильтра, автоматическое обновление должно быть заблокировано, если происходит автоматическое обновление, параметры фильтра временно отключены. Если они срабатывают одновременно, пользовательский ввод должен получить приоритет (если возможно).
Надеюсь, кто-то может помочь!
Сначала добавьте блокировку вокруг тела метода Populate
:
private object _exclusiveAccessLock = new object();
public void Populate(bool reload)
{
lock (_exclusiveAccessLock)
{
// start the job
}
}
Это поможет вам избежать состояния гонки (хотя): если я правильно понял, так как вы используете таймер Windows.Forms, он всегда срабатывает из потока Gui, поэтому они никогда не должны выполняться точно в одно и то же время).
Затем я не уверен, что вы вообще должны исключить исключение. Например, вы можете установить дополнительный флаг, который показывает, что рабочий еще не закончил, но это то, что IsBusy
должно сказать вам все равно.
Тогда есть флаг m_BlockFilter
. Я не вижу, от чего вы его настраиваете. Он также должен быть установлен внутри замка, а не в фоновом потоке, потому что в этом случае вы не можете быть уверены, что он не будет задерживаться. Вам также нужно сделать поле volatile, если вы собираетесь использовать его как флаг кросс-потока.
См. Синхронизация потоков (Руководство по программированию на С#):
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);
}