UI 元素调度程序 - 使用哪个?

UI elements dispatchers - which to use?

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

设置

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

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

实施

最简单的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);
        });

    }

问题是:我使用的调度程序有什么不同吗?我应该总是尝试使用最小范围的调度程序还是没关系,我总是可以使用最大范围的可用调度程序(在本例中为 UserControls),因为它是 UI-thread还是调度员?

您看到的 "different" Dispatcher 实际上是 WPF 中 UI 线程的同一个 Dispatcher

但是您的案例是 async 方法的经典模式。

首先,在 UI 线程(禁用按钮)上准备好所有你需要的东西。 然后等待long-运行任务。 最后,post-处理任务(启用按钮,设置输出)。

使用此方法,您无需费心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 手动整理数据。不推荐,但有时不可避免。