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。