RaiseCanExecuteChanged 在等待时调用会导致死锁

本文关键字:死锁 调用 在等待 RaiseCanExecuteChanged | 更新日期: 2023-09-27 18:30:20

我正在使用PRISM的WPF和DelegateCommand,并遇到以下问题:

我启动一个异步操作,如下所示:

public async void ProgramDevice()
{
    var result = await FirmwareLoader.DownloadFirmwareAsync();
}

在此方法中,触发了一个我注册的事件,并且应该更新我的DelegateCommand以便无法执行:

//UiCommand is of type DelegateCommand
Engine.IsProgrammedChanged += 
    (s, e) => Dispatcher.Invoke(() => UiCommand.RaiseCanExecuteChanged());

现在我遇到了问题,RaiseCanExecuteChanged导致死锁(我检查了,Dispatcher.Invoke不会导致死锁,因为当我例如显示 MessageBox 时,它工作正常)。

我是否做错了什么或如何解决此问题?

RaiseCanExecuteChanged 在等待时调用会导致死锁

我看到你已经解决了你的问题,但我想我会给出一个更通用的解决方案,帮助你防止将来出现这样的死锁。

在您的情况下,您可以使用如下ConfigureAwait轻松避免此死锁:

var result = await FirmwareLoader.DownloadFirmwareAsync().ConfigureAwait(false);

这样做是允许在与原始线程不同的线程上执行延续。这样做并不总是可能的,因为很多时候您需要在 UI 线程上执行延续,但对于这个问题,我认为情况并非如此。因此,基本上,最佳做法是始终使用ConfigureAwait(false),除非您需要从原始线程恢复执行。

本文详细解释了为什么会发生这种死锁以及如何避免它们。另一个推荐阅读是异步编程的最佳实践。

发现问题:
这不是RaiseCanExecuteChanged,而是由它触发的实际CanExecute。在那里,我有一个等待编程任务完成的AsyncLock,然后返回我用来确定是否可以执行UiCommand的值 ->编程任务触发时的死锁......

我通过使用我需要的值的"sync"属性(不使用锁,只返回当前值/统计信息)简单地解决了它。

我是否做错了什么或如何解决此问题?

  1. 方法 Dispatcher.Invoke 阻止工作线程,直到 UI 线程进行所有更新

  2. UI 线程使用一些被工作线程锁定的资源(通过上面代码中的RaiseCanExecuteChanged -> CanExecute 方法链)和块

  3. 死锁,因为工作线程等待 UI 线程完成更新,UI 线程等待工作线程释放锁定的资源

确保没有死锁的一种可能方法是使用 Dispatcher.BeginInvoke 异步调用 UI 线程上的更新

//UiCommand is of type DelegateCommand
Engine.IsProgrammedChanged += 
    (s, e) => Dispatcher.BeginInvoke(() => UiCommand.RaiseCanExecuteChanged());

这样,当工作线程释放锁定的资源时,UI 线程将等待片刻,然后进行更新。但不会有僵局。