【问题标题】:WPF Dispatcher Thread Freeze main windowWPF 调度程序线程冻结主窗口
【发布时间】:2018-02-25 11:27:39
【问题描述】:

而不是在后台工作 - 此代码仍然冻结我的程序:

private void button_Click(object sender, RoutedEventArgs e)
{
    this.Dispatcher.BeginInvoke(new Action(() =>
    {
        Thread.Sleep(5000);
        label.Content = "Done";
    }), DispatcherPriority.Normal);
}

我已经尝试过线程/任务,线程示例:

private void button_Click(object sender, RoutedEventArgs e)
{
    var t = new Thread(new ThreadStart(runtask));
    t.Start();
}

private void runtask()
{
    this.Dispatcher.BeginInvoke(new Action(() =>
    {
        Thread.Sleep(5000);
        label.Content = "Done";
    }), DispatcherPriority.Normal);
}

任务示例:

private void button_Click(object sender, RoutedEventArgs e)
{
    Task.Run(() =>
    {
        Application.Current.Dispatcher.Invoke(DispatcherPriority.Background, new Action(() =>
        {
            Thread.Sleep(5000);
            label.Content = "Done";
        }));
    });
}

我的程序仍然冻结。有什么建议吗?

【问题讨论】:

  • 是什么让你觉得BeginInvoke 在后台运行它?
  • 如果你调用 Invoke,你实际上是在主线程上..
  • @rhonin 文档意味着它是异步的从调用线程的角度来看。从UI线程的角度来看,这绝对是一个同步操作。
  • 是的,正是……

标签: c# wpf multithreading task dispatcher


【解决方案1】:

来自Dispatcher 类的documentation

提供用于管理线程工作项队列的服务。

来自Dispatcher.BeginInvokedocumentation

在创建 Dispatcher 的线程上使用指定参数异步执行指定委托。

这里“异步”指的是辅助线程,而不是主线程。因为主要的由主要的 Dispatcher 所有。这意味着,无论从哪个线程,对该 Dispatcher 的每次调用 InvokeBeginInvoke 都会将调用的 Action 放入主线程必须执行的操作队列中,但从主线程的角度来看,它们将一个接一个地同步执行。

例如,如果你在10ms内将3个Action like Thread.Sleep(1000);放到Dispatcher上,无论是Invoke还是BeginInvoke,从是否是Thread,那个Dispatcher都会让UI Thread同步执行这3个Action,所以它们总共需要 3000 毫秒。

也许关于BeginInvoke 的文档可以写得更好,比如:

在创建 Dispatcher 的线程上执行具有指定参数的指定委托。从调用线程的角度来看,指定的委托是异步执行的。

现在...InvokeBeginInvoke

使用Invoke,辅助线程对调度程序说:让我们在主线程上执行这个,并且在你的线程工作完成之前不敢返回。然后只有这样我才会继续

例如,如果你这样写:

this.Dispatcher.Invoke(new Action(() =>
    {
        Thread.Sleep(5000);
        Debug.WriteLine("After Sleep");
    }));
Debug.WriteLine("Continuation on secondary Thread");

控制台将在大约 5000 毫秒后打印:

“睡后”

“在辅助线程上继续”

改为使用BeginInvoke,线程说:“嘿,调度员,在主线程上排队这个操作,但尽快返回,以便我可以立即继续我的工作”

在这种情况下,控制台将立即打印:

“在辅助线程上继续”

大约 5000 毫秒后:

“睡后”


现在,如果您的目的是在后台执行一些繁重的操作,您应该了解 .NET 4.5 和 C# 5.0 中可用的 async/await 模式。

在你的例子中,我会写:

private async void button_Click(object sender, RoutedEventArgs e)
{
    await Task.Delay(5000); // await a heavy operation executed in background

    label.Content = "Done"; // control back to the UI Thread that executes this
}

【讨论】:

  • Async / await 听起来不错,但是如果我想在操作期间更改 UI(如进度条),则需要在代码中使用调度程序
  • @Rhonin,这就是 Bindings 为您提供帮助的地方。你有一个观点。视图与 ViewModel 耦合。 ViewModel 有一个属性string LabelContent,绑定到LabelContent 属性。然后,您可以从任何线程更新 LabelContent:绑定将负责将其分派到 UI 线程,避免出现 CrossThreadException。 WPF 太漂亮了……
【解决方案2】:

如果你的 UI 访问是你方法的最后一个,你可以使用这个小扩展。

https://mitsufu.wordpress.com/2015/08/03/dispatcher-switchto/

private void Button_Click(object sender, RoutedEventArgs e)
{
    Task.Run(async () =>
    {
        //heavy background operation
        await Dispatcher.SwitchTo();
        Title = "ok";
    });
}

【讨论】:

  • 这完全没有意义。 Binding 已经处理了主线程上的重新编组(至少对于简单的属性),并且 async/await 模式允许编写可以非常平滑地在线程之间连续切换的代码,所有这些东西都已经内置在 WPF 中和 C# 5.0。无需外部库。
  • 您好 Massimiliano,是的 WPF 会通过绑定自动完成,但我在这里没有使用任何绑定 :)(Rhonin 也不使用)关于绑定的情况,您应该谨慎对待自动同步。如果您知道您将更新一堆属性,您将多次切换上下文而无济于事,显式同步将更有效。而且,线程同步不仅仅是UI阻塞,很多人用它来序列化调用以避免冲突。即使在今天,网络上也充斥着关于 WPF 显式 UI 同步的文章。
  • @MassimilianoKraus,在这里阅读有关不同框架的 ui 线程同步的有趣信息:github.com/lbugnion/mvvmlight/tree/master/Samples/…
  • “你将多次切换上下文,显式同步会更有效”...我问的正是这个问题some time ago“许多人使用它来序列化调用以避免冲突”...是的,在这些情况下你是对的。但恕我直言,这不是一个常见的情况(也因为通常你在 UI 上放了一个微调器,所以用户不能在中间做其他事情)。谢谢你的链接,反正很有趣。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多