Dispatcher.BeginInvoke 中不确定的 ProgressBar 动画混蛋

Indeterminate ProgressBar animation jerk in Dispatcher.BeginInvoke

我有一段代码测试 GUI 和线程行为。我想保留 ProgressBar animation 运行ning (with IsIndeterminate="True") 因为我查询数据库并添加大量行 (10K+) 到 DataGrid 中。即使我将数据库和 GUI 代码包装在 Dispatcher.BeginInvoke 中,ProgressBar animation 也会随着 DataGrid 的填充而跳动。

我希望 ProgressBar 动画 会冻结(如果在 GUI 线程上)或 运行 平滑(如果在单独的线程上),但我不明白为什么动画是 运行ning jerkingly。

请不要建议BackgroundWorker,因为我想了解这个问题的问题,以及为什么BeginInvoke 没有分离线程。我只是简单地遍历 SqlDataReader 并将其作为项目逐一添加到 DataGrid,而不是数据绑定到源或数据表。

// XAML
<Button Click="Button_Click"></Button>
<ProgressBar IsIndeterminate="True"></ProgressBar>
<DataGrid ... ></DataGrid>

// C#
private void Button_Click(object sendoer, RoutedEventArgs e)
{
    this.Dispatcher.BeginInvoke(DispatcherPriority.Normal,(ThreadStart)delegate()
    {
        // Query database and update GUI (e.g. DataGrid)
    });
}

Dispatcher总是在与其关联的线程(在您的情况下为UI线程)上执行代码,无论您是否使用InvokeInvokeAsyncBeginInvoke 的 shorthand 很方便)。所以所有关于从数据库加载数据和更新 DataGrid 的工作都是在 UI 线程上完成的,因此动画不流畅。

InvokeInvokeAsync的区别在于前者同步执行,后者异步执行。这意味着在第一种情况下,调用线程将被挂起,直到委托完成执行(即它将 synchronized),而在第二种情况下,线程将继续执行无需等待代表完成。让我试着用例子来指出这个区别。

示例 I. 这些方法是从 UI 线程调用的(如您的情况)

假设我们只有一个线程(UI 线程)。调用 Invoke 不会有任何明显的效果,因为委托会立即执行,然后才会继续执行。所以这个:

Dispatcher.Invoke(() => DoSomeStuff());
DoSomeOtherStuff();

将具有与此相同的效果:

DoSomeStuff();
DoSomeOtherStuff();

但是调用BeginInvoke会产生这样的效果,即只有在执行了所有具有更高优先级(或已经以相同优先级调度)的计划任务后,委托才会被安排执行。所以在这种情况下:

Dispatcher.InvokeAsync(() => DoSomeStuff());
DoSomeOtherStuff();

DoSomeOtherStuff() 将首先执行,DoSomeStuff() 第二。这通常用于例如事件处理程序中,其中您需要仅在事件完全处理后才执行一些代码(例如 see this question)。

示例 II. 从不同的线程调用方法

假设我们有两个线程 - UI 线程和一个工作线程。如果我们从工作线程调用 Invoke

Dispatcher.Invoke(() => DoSomeStuff());
DoSomeOtherStuff();

首先DoSomeStuff()会在UI线程上执行,然后DoSomeOtherStuff()会在工作线程上执行。如果 InvokeAsync:

Dispatcher.InvokeAsync(() => DoSomeStuff());
DoSomeOtherStuff();

我们只知道 DoSomeStuff() 将在 UI 线程上执行,而 DoSomeOtherStuff() 将在工作线程上执行,但它们执行的顺序是不确定的*。

通常当您的委托产生一些结果并且您需要它继续在工作线程上执行时使用 Invoke(例如,当您需要获得依赖项 属性 值时)。另一方面,InvokeAsync 通常在委托未产生任何结果(或结果被忽略)时使用,例如在您的情况下 - 更新 DataGrid 不会产生任何值得等待的结果,因此您可以立即继续加载下一批数据。

我希望这能为您阐明问题,您会明白为什么 "jerky UI" 的解决方案是将繁重的工作委托给另一个线程,并且只使用调度程序与 UI 交互。这是使用 BackgroundWorkerTask 的建议。

*其实他们很可能会同时执行。我的意思是,例如,如果这两种方法都只向控制台打印一些文本,则控制台中消息的顺序是不确定的。