简单任务:更新在事件上执行按钮的能力。
我使用MVVM 工具包编写了以下基本代码:
public partial class MainViewModel : ObservableObject
{
public MainViewModel()
{
_ = Task.Run(async () =>
{
try
{
while (true)
{
await Task.Delay(1000);
CanTest = !CanTest;
}
}
catch (Exception e)
{
Debug.WriteLine(e.Message);
}
});
}
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(TestCommand))]
private bool _canTest;
[RelayCommand(CanExecute = nameof(CanTest))]
public void Test()
{
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
}
如您所见,这里创建了一个简单的类,它在后台旋转某个任务,并每秒更新一次属性的状态bool
,根据该状态更新命令CanExecute
。
为清楚起见,此代码将生成的内容:
public bool CanTest
{
get => _canTest;
set
{
if (!EqualityComparer<bool>.Default.Equals(_canTest, value))
{
OnCanTestChanging(value);
OnCanTestChanging(default, value);
OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.CanTest);
_canTest = value;
OnCanTestChanged(value);
OnCanTestChanged(default, value);
OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.CanTest);
TestCommand.NotifyCanExecuteChanged();
}
}
}
public IRelayCommand TestCommand => testCommand ??= new RelayCommand(new Action(Test), () => CanTest);
也就是说,许多人都熟悉的INotifyPropertyChanged
and的最基本实现。ICommand
现在问题的症结在于:
这段代码导致错误
调用线程无法访问此对象,因为另一个线程拥有此对象。
这是可以理解的,我们要离开 UI 线程,这就是为什么我们不能那么容易地使用 UI,但是如何返回呢?对于这个问题,我们有典型的解决方案,使用Dispatcher
. 好吧,就说这个吧ViewModel
,一个对UI一无所知的独立类,我们可以这样写一个拐杖
public MainViewModel(Dispatcher dispatcher)
{
_ = Task.Run(async () =>
{
try
{
while (true)
{
await Task.Delay(1000);
dispatcher.BeginInvoke(new Action(() => { CanTest = !CanTest; }));
}
}
catch (Exception e)
{
Debug.WriteLine(e.Message);
}
});
}
这将解决问题,但仅此违反了 MVVM 和其他规则。使用Dispatcher.CurrentDispatcher
我们也将摆脱错误,但我们的数据将不再更新,这似乎也是合乎逻辑的。其实,如何解决问题呢?
是的,属性更改本身不会导致错误,它会导致更新CanExecute
(调用:TestCommand.NotifyCanExecuteChanged();),从而生成[NotifyCanExecuteChangedFor(nameof(TestCommand))]
. 如果移除,按钮将不会实时更新,但bool
属性值会开始变化。
PS无限await
循环例如,在一个真实的项目中,我有一个服务监视进程变化(运行或不运行),并向外发送一个事件,它都在容器中注册,它从中请求ViewModel,它订阅和更新属性,收到错误。
public class ProcessWatcherService : IProcessWatcher, IDisposable
{
private readonly HashSet<string> _monitoredProcesses = new();
public event EventHandler<ProcessChangedEventArgs>? ProcessChanged;
private readonly ManagementEventWatcher _startWatch;
private readonly ManagementEventWatcher _stopWatch;
private readonly ILogger<ProcessWatcherService> _logger;
public ProcessWatcherService(ILogger<ProcessWatcherService> logger)
{
_logger = logger;
_startWatch = new ManagementEventWatcher(new WqlEventQuery("SELECT * FROM Win32_ProcessStartTrace"));
_startWatch.EventArrived += (_, e) => ProcessChangedEvent(e.NewEvent.Properties["ProcessName"].Value, ProcessStatus.Started);
_startWatch.Start();
_stopWatch = new ManagementEventWatcher(new WqlEventQuery("SELECT * FROM Win32_ProcessStopTrace"));
_stopWatch.EventArrived += (_, e) => ProcessChangedEvent(e.NewEvent.Properties["ProcessName"].Value, ProcessStatus.Stopped);
_stopWatch.Start();
}
public bool Verify(string process)
{
var result = Process.GetProcesses()
?.Any(x => x.ProcessName.ToLower() == Path.GetFileNameWithoutExtension(process).ToLower()) == true;
_logger.LogTrace("Verify process status {process} = {result}", process, result);
return result;
}
public void StartWatch(string process)
{
_logger.LogDebug("Process {process} added to watch list", process);
_monitoredProcesses.Add(process.ToLower());
}
public bool AddAndWatch(string process)
{
StartWatch(process);
return Verify(process);
}
void ProcessChangedEvent(object name, ProcessStatus status)
{
if (name is string processName && _monitoredProcesses.Contains(processName.ToLower()))
{
_logger.LogInformation("Process {process} change status: {status}", processName, status);
ProcessChanged?.Invoke(this, new(processName, status));
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
_startWatch.Stop();
_stopWatch.Stop();
}
}