【问题标题】:Indeterminate ProgressBar animation jerk in Dispatcher.BeginInvokeDispatcher.BeginInvoke 中的不确定 ProgressBar 动画混蛋
【发布时间】:2017-06-07 06:33:37
【问题描述】:

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

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

请不要建议 BackgroundWorker,因为我想了解这个问题中的问题,以及为什么 BeginInvoke 没有分离线程。我只是循环遍历 SqlDataReader 并作为 Item 一项一项添加到 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)
    });
}

【问题讨论】:

  • 进度条动画在 UI 线程上运行。 WinRT 能够在不同的线程上运行动画,但不幸的是,这对于 WPF 仍然不可用。想知道它为什么会跑得快,你是如何将数据绑定到 DataGrid 的?
  • 我只是简单地遍历 SqlDataReader 并作为 Item 一项一项地添加到 DataGrid,而不是将数据绑定到源或数据表。
  • 在您的代码示例中,您都在 UI 线程上工作(使用 BeginInvoke),那么为什么会顺利呢?
  • 也许我弄错了——但对我来说很明显,在调度程序线程上运行东西会导致 UI 变为jerk。你不能在 Dispatcher-Thread 中查询和更新你的 Database async 吗?

标签: c# wpf multithreading dispatcher


【解决方案1】:

Dispatcher always 在与其关联的线程(在您的情况下为 UI 线程)上执行代码,无论您使用 Invoke 还是 InvokeAsync(这是一个方便BeginInvoke 的简写)。所以所有关于从数据库加载数据和更新DataGrid的工作都是在UI线程上完成的,因此动画并不流畅。

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

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

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

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

将具有与此相同的效果:

DoSomeStuff();
DoSomeOtherStuff();

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

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

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

示例二。方法是从不同的线程调用的

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

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

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

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

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

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

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

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

【讨论】:

  • '@Grx70' 解释清楚,谢谢。由于应用程序在 UIThread 上启动,所有属于或拥有 UIThread 的东西(例如 Main() 中的 new Thread() )。所以'Dispatcher'不会创建线程,但它的功能是简单地在同一个线程上同步或异步地优先处理不同的工作,或者允许UI拥有的线程访问UIThread。那么'BackgroundWorker'也是如此吗?因为如果我在 BGW 的 DoWork 上调用 Dispatcher.BeginInvoke 来更新 UIThread 我仍然会遇到线程异常。你能帮助澄清这种理解吗?
  • 是的,dispatcher 也应该是与BackgroundWorker 一起使用的安全选择。如果您对此有疑问,最好提出新问题并提供MCVE
猜你喜欢
  • 1970-01-01
  • 2013-09-03
  • 1970-01-01
  • 2010-11-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多