我在使数据加载和过滤线程安全方面遇到了一些问题。
我控件的基类上的以下代码,它通过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);
}
}
主窗体上的Timer每60秒运行一次,并将true传递给Populate方法。将重新加载作为trues传递告诉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);
}
}
据我所知,当我设法在Timer触发填充事件的同时单击过滤器选项时,会发生错误。我认为Populate方法中缺少某些东西,即锁定,但我不确定如何在这种情况下正确使用它。
代码有利于用户输入。如果用户选择过滤器选项,则应阻止自动更新,如果自动更新触发,则暂时禁用过滤器选项。如果它们同时触发,则用户输入应优先(如果可能)。
希望有人可以提供帮助!
答案 0 :(得分:2)
首先,在Populate
方法正文周围添加一个锁:
private object _exclusiveAccessLock = new object();
public void Populate(bool reload)
{
lock (_exclusiveAccessLock)
{
// start the job
}
}
这将帮助您避免竞争条件(虽然:如果我做对了,因为您使用的是Windows.Forms计时器,它总是会从Gui线程触发,因此它们永远不会完全同时执行。)
接下来,我不确定你是否应该抛出异常。例如,您可以设置一个额外的标志,告诉您工作人员尚未完成,但这正是IsBusy
应该告诉您的。
然后是m_BlockFilter
标志。我无法看到你在哪里设置它。它也应该设置在锁内,而不是在后台线程中,因为在这种情况下你不能确定它不会被延迟。如果要将其用作跨线程标记,还需要创建字段volatile。
答案 1 :(得分: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.
}
}
}
编辑:您不希望两个线程进入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);
}