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);
    }
}

主窗体上的 Timer 每 60 秒运行一次,并将 true 传递给 Populate 方法。将 reload 作为 true 传递告诉 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 方法中缺少一些东西,即锁,但我不确定如何在这种情况下正确使用它。

该代码有利于用户输入。如果用户选择过滤器选项,则应阻止自动更新,如果触发自动更新,则暂时禁用过滤器选项。如果它们同时触发,则用户输入应优先(如果可能)。

希望有人能帮忙!

4

2 回答 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 。

于 2009-07-03T18:26:26.620 回答
1

请参阅线程同步(C# 编程指南)

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);
}
于 2009-07-03T18:23:25.360 回答