WPF:当队列中什么都没有被调用时,如何调用Dispatcher.BeginInvoke * *?

时间:2010-12-16 21:37:18

标签: wpf multithreading backgroundworker dispatcher

我在WPF应用程序中有一个导入文件方法,它读取文件并在数据库中插入一些记录。

此方法在BackgroundWorker对象中运行。 我在Dispatcher.Invoke电话中更新了进度条。如果我按原样运行,导入200k记录需要大约1分钟,如果我没有显示任何进度,则只需4到5秒!如果我使用Dispatcher.BeginInvoke Background优先级,则需要相同的4到5秒,但进度条+计数器正在更新,需要大约1分钟。因此,很明显,UI就是问题所在。

另一个问题是我需要显示进度,所以我在考虑是否有任何方法可以使用Dispatcher.BeginInvoke但首先检查队列中是否有任何内容,如果有,我只是跳过它,表现如下:在第1秒,1%完成,2秒后完成50%,在第4秒完成100%)。

对此有何帮助?

感谢!!!

4 个答案:

答案 0 :(得分:2)

没有看到代码就不可能说,但是

  

我在Dispatcher.Invoke调用

中更新了一个进度条

为什么呢?这就是ReportProgress的用途。

如果我不得不猜测(我确实如此),我会说你经常报告进展情况。例如,不要在每条记录之后报告进度,而是在批次为100或其他任何记录之后报告进度。

答案 1 :(得分:2)

问题是您的回调正在Dispatcher上排队。每一个都会导致屏幕重新绘制,并且因为它们处于后台优先级,所以下一个将在处理之前等待重绘完成,因此每次回调都需要重新绘制一次,这可能很慢。

不要试图等到调度程序队列中没有任何内容,而是等到上一个进度回调处理完之后才发布新的进程回调。这将确保您一次不会有多个活动,因此无法排队。

您可以通过在发布回调时设置标记并在处理完后将其清除来执行此操作。例如:

private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
    var pending = false;
    for (int i = 0; i < 1000000; i++)
    {
        // Do some work here
        // ...
        // Only report progress if there is no progress report pending
        if (!pending)
        {
            // Set a flag so we don't post another progress report until
            // this one completes, and then post a new progress report
            pending = true;
            var currentProgress = i;
            Dispatcher.BeginInvoke(new Action(() =>
            {
                // Do something with currentProgress
                progressBar.Value = currentProgress;
                // Clear the flag so that the BackgroundWorker
                // thread will post another progress report
                pending = false;
            }), DispatcherPriority.Background);
        }
    }
}

答案 2 :(得分:2)

我只是在后台线程中更新进度计数器(它只写入计数器),并且每500毫秒左右读取一次UI(仅读取)...没有理由更新那。此外,因为一个线程是只写的,一个是只读的,所以不需要线程问题。代码变得更加简单,更清晰,更易于维护。

-Chert Pellett

答案 3 :(得分:0)

我刚刚解决了相同的情况,但使用BeginInvoke返回的对象,我认为它也非常优雅!

DispatcherOperation uiOperation = null;
while (…)
{
    …
    if (uiOperation == null || uiOperation.Status == DispatcherOperationStatus.Completed || uiOperation.Status == DispatcherOperationStatus.Aborted)
    {
        uiOperation = uiElement.Dispatcher.BeginInvoke(…);
    }
}

进度条变得有点粗糙(不太平滑),但它飞得很快。在我的例子中,代码使用StreamReader.ReadLine()从文本文件中逐行解析。在读取每一行之后更新进度条将导致读取操作在进度条甚至中途填充之前完成。使用同步Dispatcher.Invoke(…)会将整个操作减慢到100 KiB / s,但进度条会准确跟踪进度。使用上面的解决方案,我的应用程序在一秒内解析了8,000 KiB,只有3个进度条更新。

使用BackgroundWorker.ReportProgress(…)的一个区别是进度条可以在较长时间运行的操作中显示更精细的细节。 BackgroundWorker.ReportProgress(…)仅限于以0%到100%的增量报告进度。如果您的进度条代表超过100个操作,则需要更精细的值。当然,这也可以通过不使用percentProgress参数并将userState传递给BackgroundWorker.ReportProgress(…)来实现。