UI元素分派器-使用哪个?

时间:2018-11-28 15:16:32

标签: c# wpf dispatcher ui-thread

我试图完全理解在C#WPF应用程序中使用调度程序的机制,并提出了我找不到答案的问题。我希望社区能够为我提供帮助。

设置

想象一下,我们有一个带有按钮标签的用户控件。按下按钮后,一些耗时的操作开始,一旦完成,它将结果(例如,执行持续时间)放置在标签中。

为了获得良好的用户体验,应满足以下条件:

  • 耗时的操作正在运行时,UI必须保持响应状态
  • 为了防止用户多次并行运行耗时的操作,应禁用
  • 按钮。 耗时的操作完成后,该功能就会启用。

实施

最简单的xaml

<UserControl x:Class="WhichDispatcher">
    <StackPanel Orientation="Horizontal">
        <Button x:Name="button" Content="Execute time-consuming operation" Click="button_Click" />
        <Label x:Name="label" Content="Not yet executed" />
    </StackPanel>
</UserControl>

以及背后的代码:

public partial class WhichDispatcher : UserControl
{
    public UserControl1()
    {
        InitializeComponent();
    }

    private void button_Click(object sender, RoutedEventArgs e)
    {
        this.button.IsEnabled = false;
        Task.Run(() => 
        {
            this.TimeConsumingOperation();
        });
    }

    private void TimeConsumingOperation()
    {
        throw new NotImplementedException();
    }
}

调用耗时操作的方法将在单独的线程上运行,以防止UI锁定。

耗时的操作

我现在重点介绍将执行耗时的操作的方法的实现。完成后,需要在调度程序中更新UI元素,因为无法从另一个线程调用它们。

    private void TimeConsumingOperation()
    {
        TimeSpan runningTime = new TimeSpan();
        // Run the time-consuming operation
        // Let's assume that the runningTime variable will be set as a result of the above operation.

        this.Dispatcher.Invoke(() => 
        {
            this.button.IsEnabled = true;
            this.label.Content = string.Format("The time-consuming operation took {0} seconds", runningTime.TotalSeconds);
        });
    }

UI更新将在UserControl的调度程序中执行。现在开始提问。

问题

UI更新也可以在元素的分派器中单独执行,如下所示:

    private void TimeConsumingOperation()
    {
        TimeSpan runningTime = new TimeSpan();
        // Run the time-consuming operation
        // Let's assume that the runningTime variable will be set as a result of the above operation.

        this.button.Dispatcher.Invoke(() => 
        {
            this.button.IsEnabled = true;
        });

        this.label.Dispatcher.Invoke(() =>
        {
            this.label.Content = string.Format("The time-consuming operation took {0} seconds", runningTime.TotalSeconds);
        });

    }

问题是:我使用的调度程序有什么区别?我是否应该总是尝试使用最小范围的调度程序,还是没关系,并且我始终可以选择可用的最大范围调度程序(在这种情况下为UserControl,因为它还是UI线程调度程序吗?< / p>

1 个答案:

答案 0 :(得分:1)

您看到的“不同” Dispatcher实际上是WPF中UI线程的同一Dispatcher

但是您的情况是async方法的经典模式。

首先,在UI线程上准备所需的所有内容(禁用按钮)。 然后等待长时间运行的任务。 最后,对任务进行后处理(启用按钮,设置输出)。

您无需使用这种方法来烦扰Dispatcher

private async void button_Click(object sender, RoutedEventArgs e)
{
    this.button.IsEnabled = false;
    TimeSpan runningTime = new TimeSpan();

    await Task.Run(() => 
    {
        this.TimeConsumingOperation();
    });

    this.label.Content = string.Format("The time-consuming operation took {0} seconds", runningTime.TotalSeconds);
    this.button.IsEnabled = true;
}

但是,如果要从在后台线程上执行的长时间运行的任务中与UI 进行交互,则需要考虑以下几点:

  • 使用MVVM模式和数据绑定。 WPF将自动在线程之间封送数据。
  • 使用Dispatcher手动封送数据。不推荐,但有时不可避免。