UI 元素调度程序 - 使用哪个?
UI elements dispatchers - which to use?
我试图完全理解在 C# WPF 应用程序中使用调度程序背后的机制,并提出了我找不到答案的问题。我希望社区能帮助我。
设置
假设我们有一个带有 按钮 和 标签 的用户控件。按下按钮后,一些 耗时的操作 开始,一旦完成,它将结果(例如,执行持续时间)放入标签中。
为了良好的用户体验,应满足以下条件:
- 虽然 耗时的操作 是 运行ning,但 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
s),因为它是 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
手动整理数据。不推荐,但有时不可避免。
我试图完全理解在 C# WPF 应用程序中使用调度程序背后的机制,并提出了我找不到答案的问题。我希望社区能帮助我。
设置
假设我们有一个带有 按钮 和 标签 的用户控件。按下按钮后,一些 耗时的操作 开始,一旦完成,它将结果(例如,执行持续时间)放入标签中。
为了良好的用户体验,应满足以下条件:
- 虽然 耗时的操作 是 运行ning,但 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
s),因为它是 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
手动整理数据。不推荐,但有时不可避免。