C# 后台工作人员仅在完成时报告进度

C# 后台工作人员仅在完成时报告进度,c#,winforms,progress-bar,backgroundworker,C#,Winforms,Progress Bar,Backgroundworker,我有一个非常简单的应用程序,可以将包含数千行的excel文件加载到DataGridView组件中。然后,按下“扫描”按钮,扫描此表以查找副本和各种其他问题。我选择在BackgroundWorker中运行这项密集型任务,以便UI保持响应性,并报告其进度。“我的扫描”按钮的代码只是调用后台工作程序组件上的RunWorkerAsync()方法,然后该组件执行以下操作: Scanner scanner = new Scanner(this, dgvScancodes, dgvErroredScancod

我有一个非常简单的应用程序,可以将包含数千行的excel文件加载到DataGridView组件中。然后,按下“扫描”按钮,扫描此表以查找副本和各种其他问题。我选择在BackgroundWorker中运行这项密集型任务,以便UI保持响应性,并报告其进度。“我的扫描”按钮的代码只是调用后台工作程序组件上的RunWorkerAsync()方法,然后该组件执行以下操作:

Scanner scanner = new Scanner(this, dgvScancodes, dgvErroredScancodes, BgwScanner);
foreach (DataGridViewRow row in table.Rows)
{
    //Long computations on each row

    worker.ReportProgress(row.Index / table.RowCount * 100);
}
这将调用另一个类来执行实际工作,并将扫描仪作为参数传递。然后Scanner类执行以下操作:

Scanner scanner = new Scanner(this, dgvScancodes, dgvErroredScancodes, BgwScanner);
foreach (DataGridViewRow row in table.Rows)
{
    //Long computations on each row

    worker.ReportProgress(row.Index / table.RowCount * 100);
}
扫描执行得非常好,并产生预期的输出,但我的ProgressBar在BackgroundWorker的ProgressChanged事件中从未使用以下代码进行更新:

private void BgwScanner_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    pgbProgress.Value = e.ProgressPercentage;
}
考虑到这可能是我计算百分比的方式的问题,我替换了对ReportProgress的调用,只发送了一个值50,以查看会发生什么。进度条会更新,但只有在整个扫描完成时才会更新!没有引发异常,它的行为就像UI线程忙于做其他事情一样(除了循环表中的每一行之外,据我所知,这不是最好的)。知道我为什么会看到这种行为吗

******编辑******


我找到了罪犯。我忘记了,在扫描过程中,可以使用工具提示和背景色更新表的行。我对这两行进行了注释,可以肯定的是,进度条现在工作得非常好。这证明了我的理论,即UI线程确实被淹没了。有没有可能解决这个问题?进度条更新的某种更高优先级?

对于整数除法,值向零舍入到下一个最接近的整数。 在您的例子中,它在被乘以100之前总是向零取整,因为row.index大概在0和table.RowCount-1之间

非常冗长:

row.Index / table.RowCount * 100
可能成为:

(int)(((double)row.Index / (double)table.RowCount) * 100)

我通过在表的底层数据源上进行处理来解决这个问题,直到最后一分钟才需要显示它。这样,在显示表之前不会有UI更新。我仍然需要遍历表本身,以便在最后为某些单元格设置背景颜色和工具提示(因为您显然无法在数据源本身上执行此操作),但与之前在数据源上执行的处理相比,这需要相对较短的时间。我的ProgressBar现在工作正常

******编辑******

实际上,您可以使用DataGridView的CellFormatting事件为单元格着色,无需对行进行迭代,这样做的额外效果是在重新排序表时保留工具提示和背景色:

private void dgvErroredScancodes_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
    DataGridViewCell scancodeCell = dgvErroredScancodes.Rows[e.RowIndex].Cells[1];

    if (e.Value.Equals((int) ErrorType.DUPLICATE))
    {
        scancodeCell.Style.BackColor = ErrorType.DUPLICATE.BackgroundColor();
        scancodeCell.ToolTipText = ErrorType.DUPLICATE.ErrorMessage();
    }
    ...
}

如果没有一个可靠地再现问题的好方法,就不可能说。也就是说,您应该看看关于堆栈溢出的数百个其他问题,这些问题涉及到
ReportProgress()
未能达到预期效果。我向你保证,你的问题不是唯一的,而且以前在这里已经得到了回答。你应该检查的第一件事是确保你确实设置了
BackgroundWorker.WorkerReportsProgress
属性。你是对的,我读了几十篇文章,最后其中一篇给我指出了正确的方向。查看我的编辑。还有许多问题已经涉及到后台调用使UI线程饱和(以及您也遇到的“整数分割”问题)。您可以阅读这些内容以了解更多详细信息,但简短的版本只是为了限制更新UI的频率。最多每100-200毫秒更新一次是用户认为最有用的,在许多情况下,更新的频率甚至可以更低,而不会损失任何可用性。明白,但就我而言,我不确定如何实现这一点。扫描过程在表格中循环并为某些单元格着色/为有问题的行添加工具提示文本。更新此表的操作被编织到扫描过程中。我会看看我是否能找到解决这个问题的方法,任何建议都会有帮助。谢谢“我不知道如何才能做到这一点”——你有没有看看其他人是如何解决这个问题的?“更新此表是在扫描过程中进行的”——如果数据层与UI层的耦合过于紧密/紧密,那么肯定会使实现好的解决方案变得更加困难。也许你需要做的第一件事就是把这些层抽象得更抽象一些,这样它们实际上就不会编织得那么紧密了。在任何情况下,最终都会将从后台线程到UI线程的转换次数和频率降到最低。这确实是问题的一部分,但我找到了进度只在最后更新的真正原因。请参阅我的编辑。