昨天的面试问题
1、之前面试的时候,面试官说了在异步更新UI不用使用Dispatcher.CurrentDispatcher.Invoke
我之前倒没注意,或者说知识浅薄,不知道,
<StackPanel>
<Button Content="执行耗时任务" Margin="20"
Command="{Binding ProgressCommand}"></Button>
<ProgressBar Margin="50" x:Name="progressBar1" Width="300" Height="20" Value="{Binding ProgressValue}"/>
</StackPanel>
public class MainWindowViewModel:BindableBase
{
public double _progressValue;
public double ProgressValue
{
get => _progressValue;
set => SetProperty(ref _progressValue, value);
}
public MainWindowViewModel()
{
ProgressCommand = new DelegateCommand(ExecuteProgress);
}
private async void ExecuteProgress()
{
for (int i = 0; i <= 100; i++)
{
await Task.Delay(50); // 更新频率根据需要调整
ProgressValue = i;
// Dispatcher.CurrentDispatcher.Invoke(() => ProgressValue=i); // 更新ProgressBar的值
}
}
public ICommand ProgressCommand { get; set; }
}
上面代码不使用Dispatcher.CurrentDispatcher.Invoke(() => ProgressValue=i); 也是成功的
原因是
在WPF中,使用属性通知(如INotifyPropertyChanged接口)更新UI时,即使在异步操作中,通常不需要使用Dispatcher.Invoke,因为WPF的数据绑定机制会处理线程的安全性。以下是几个关键点来解释为什么您的代码能够成功更新UI:
数据绑定的线程安全:
WPF的数据绑定是线程安全的。这意味着,当您通过属性通知更改绑定属性的值时,WPF会确保属性更改通知(OnPropertyChanged)在主UI线程上被调用,即使属性的实际更改发生在后台线程。
属性更改通知机制:
当您设置属性(如ProgressValue)并调用SetProperty方法时,WPF会触发OnPropertyChanged事件。如果该属性已经绑定到UI元素,WPF将自动在主UI线程上处理这个事件,并更新相应的UI元素。
async和await的使用:
使用async和await关键字时,当await一个任务(如Task.Delay)时,执行会返回到调用者,直到任务完成。当任务完成后,剩余的代码会在原始的上下文中继续执行。如果原始上下文是UI线程,那么代码会继续在UI线程上执行,因此可以直接更新UI而不需要Dispatcher.Invoke。
DelegateCommand的执行:
当您使用DelegateCommand或类似的命令执行机制时,如果命令是在UI线程上触发的(例如,通过按钮点击),那么命令的执行方法(在这个例子中是ExecuteProgress)也会在UI线程上开始执行。即使该方法包含异步操作,异步操作完成后的继续执行也会在原始的UI线程上进行。
SetProperty的内部机制:
SetProperty方法内部会检查是否需要跨线程访问UI元素。如果需要,它会自动使用Dispatcher.Invoke来确保线程安全。因此,即使在后台线程上,调用SetProperty也是安全的。
综上所述,WPF的数据绑定机制确保了在异步操作中更新绑定属性的线程安全性,因此您通常不需要手动使用Dispatcher.Invoke。只要属性更改通知被正确触发,WPF就会处理剩余的工作,包括在适当的线程上更新UI。当然,如果需要执行更复杂的UI操作,或者在没有绑定的情况下直接操作UI元素,那么可能需要使用Dispatcher.Invoke或BeginInvoke。