2 BackgroundWorker组件的问题

时间:2009-11-26 10:12:32

标签: winforms datagridview backgroundworker

首先,我知道我应该使用正确的线程技术(Threadpool,BeginInvoke等)来实现这一目标,但这有点超过我的头脑,并会花一些时间来阅读材料并了解它(如果您的方案有任何URL引用,请随时发布)。

在过渡期间,我使用backgroundWorker来拉取一个非常大的结果集并用它填充DatagridView。我在DoWork事件中成功创建了SortableBindingList<TEntities>并将结果传递出去。在RunWorkerCompleted事件中,我将SortableBindingList<TEntities>转换并绑定到我的网格。我关注的两个主要领域如下:

1)访问私有变量。 我想将两个参数List<long>中的一个传递给我的DoWork事件,但根据传递给它的列表运行不同的查询。我可以通过声明一个类级别的私有布尔变量来解决这个问题。这似乎很愚蠢,但在我的DoWork我是否可以访问该私有变量并相应地路由查询? (我已经测试了这个,它确实有效,没有任何错误弹出)

private bool SearchEngaged = false;

private void bgw_DoWork(object sender, DoWorkEventArgs e) {
    BackgroundWorker worker = sender as BackgroundWorker;
    e.Result = GetTasks((List<long>)e.Argument, worker, e);
}
SortableBindingList<Task> GetTasks(List<long> argsList, BackgroundWorker worker, DoWorkEventArgs e) {
    SortableBindingList<Task> sbl = null;
    if (worker.CancellationPending) {
        e.Cancel = true;
    }
    else {
        if (SearchEngaged) {
            sbl = DU.GetTasksByKeys(argsList);
        }
        else {
            sbl = DU.GetTasksByDivision(argsList);
        }
    }
    return sbl;
}

2)UI线程在RunWorkerCompleted开始时冻结。 好的,我知道我的用户界面在DoWork事件期间有响应,'如果我绑定,我需要+/- 2秒才能运行并返回SortableBindingList<Task>列出到网格,但只填充它。但是,当我将其绑定到Grid时,我的UI会冻结,我在RunWorkerCompleted事件中执行此操作。请记住,我的网格有4个图像列,我在CellFormatting处理。此过程需要额外的8秒才能完成,在此期间,我的UI完全是非交互式的。我知道这样做的跨线程含义,但是否有任何方法可以在后台完成网格填充和格式化,或者不会导致我的UI冻结? RunWorkeCompleted看起来像这样:

private void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
    if (e.Cancelled) {
        lblStatus.Text = "Operation was cancelled";
    }
    else if (e.Error != null) {
        lblStatus.Text = string.Format("Error: {0}", e.Error.Message);
    }
    else {
        SortableBindingList<Task> sblResult = (SortableBindingList<Task>)e.Result;
        dgv.DataSource = sblResult;
        dgv.Enabled = true;
        TimeSpan Duration = DateTime.Now.TimeOfDay - DurationStart;
        lblStatus.Text = string.Format("Displaying {0} {1}", sblResult.Count, "Tasks");
        lblDuration.Visible = true;
        lblDuration.Text = string.Format("(data retrieved in {0} seconds)", Math.Round(Duration.TotalSeconds, 2));
        cmdAsyncCancel.Visible = false;
        tmrProgressUpdate.Stop();
        tmrProgressUpdate.Enabled = false;
        pbStatus.Visible = false;
    }
}

对于冗长的查询感到抱歉,但我将非常感谢您的回复!谢谢!

2 个答案:

答案 0 :(得分:1)

您的代码似乎做得非常正确。

至于UI线程更新屏幕所需的8秒,你无能为力。请参阅我对this question的回答。

要优化用户界面部分,您可以尝试在网格或其包含的面板上调用SuspendLayoutResumeLayout

您还可以尝试减少数据绑定期间完成的处理量。例如:

  • 网格中完成的计算可以移动到数据模型中(从而在工作线程中完成)。
  • 如果网格根据数据模型自动计算其列,则尝试对其进行硬编码。
  • 编辑:将业务层中的数据设为页面,并使网格一次只显示少量行。

答案 1 :(得分:1)

我认为对您的问题最简单的解决方案是使用您自己提到的DoWorkRunWorkerCompleted而不是Dispatcher.BeginInvoke设置网格的数据源。像这样:

private bool SearchEngaged = false;

private void bgw_DoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker worker = sender as BackgroundWorker;
    SortableBindingList<Task> sblResult = GetTasks((List<long>)e.Argument, worker, e);

    BeginInvoke((Action<object>)(o => dataGridView1.DataSource = o), sblResult);
}

private void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    if (e.Cancelled) {
        lblStatus.Text = "Operation was cancelled";
    }
    else if (e.Error != null) {
        lblStatus.Text = string.Format("Error: {0}", e.Error.Message);
    }
    else
    {
        dgv.Enabled = true;
        TimeSpan Duration = DateTime.Now.TimeOfDay - DurationStart;
        lblStatus.Text = string.Format("Displaying {0} {1}", sblResult.Count, "Tasks");
        lblDuration.Visible = true;
        lblDuration.Text = string.Format("(data retrieved in {0} seconds)", Math.Round(Duration.TotalSeconds, 2));
        cmdAsyncCancel.Visible = false;
        tmrProgressUpdate.Stop();
        tmrProgressUpdate.Enabled = false;
        pbStatus.Visible = false;
    }
}

就私人变量问题而言,我不认为你的情况会有任何问题。如果您使用某些UI事件更改它,只需将私有字段标记为volatile。可以在此处找到volatile关键字的文档:

http://msdn.microsoft.com/en-us/library/x13ttww7.aspx