简体   繁体   English

等待时调用RaiseCanExecuteChanged导致死锁

[英]RaiseCanExecuteChanged called while await causes deadlock

I am using WPF and DelegateCommand from PRISM and have the following problem: 我正在使用WPF和PRISM的DelegateCommand并遇到以下问题:

I start an async operation like: 我开始一个异步操作,如:

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

Inside this method an event is fired which I registered to and should update my DelegateCommand so it can't be executed: 在此方法内触发了一个事件,该事件已注册并应更新我的DelegateCommand因此无法执行:

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

Now I have the problem, that the RaiseCanExecuteChanged causes a deadlock (I checked and the Dispatcher.Invoke does not cause it, because when I eg show a MessageBox instead it works fine). 现在我有一个问题, RaiseCanExecuteChanged导致死锁(我检查了并且Dispatcher.Invoke不会导致死锁,因为当我例如显示一个MessageBox时,它可以正常工作)。

Am I doing something wrong or how can I work around this problem? 我是在做错什么还是该如何解决这个问题?

I see you've already solved your problem, but I thought I'd give a more general solution that will help you prevent such deadlocks in the future. 我看到您已经解决了您的问题,但是我想我会提供一个更通用的解决方案,以帮助您将来防止此类死锁。

In your case, you could easily avoid this deadlock by using ConfigureAwait like this: 在您的情况下,可以通过使用ConfigureAwait这样轻松地避免此死锁:

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

What this does is allows the continuation to be performed on a different thread than the original. 这样做是允许继续执行与原始线程不同的线程。 Doing so is not always possible, since a lot of times you need the continuation to be performed on the UI thread, but for this question I don't believe that's the case. 这样做并非总是可能的,因为很多时候您需要在UI线程上执行继续操作,但是对于这个问题,我认为并非如此。 So basically, the best practice is to always use ConfigureAwait(false) unless you need to resume execution from the original thread. 因此,基本上,最佳实践是始终使用ConfigureAwait(false)除非您需要从原始线程恢复执行。

This article explains in detail why these kind of deadlocks happen and how to avoid them. 本文详细解释了为什么会发生这种死锁以及如何避免死锁。 Another recommended read is Best Practices in Asynchronous Programming . 另一本推荐的读物是“异步编程最佳实践”

Found the problem: 发现问题:
It was not the RaiseCanExecuteChanged , but the actual CanExecute which is triggered by it. 不是RaiseCanExecuteChanged ,而是由它触发的实际CanExecute In there I had an AsyncLock which waited for the programming task to be finished, before returning the value I use to descide if UiCommand can be executed --> deadlock as the programming task triggered it... 在这里我有一个AsyncLock ,它等待编程任务完成,然后返回用于确定是否可以执行UiCommand的值->死锁,因为编程任务触发了它...

I solved it by simple using the "sync" property (which does not use the lock and just returns the current value/stat) of the value I need. 我通过使用所需值的“ sync”属性(不使用锁,仅返回当前值/ stat)来简单地解决了该问题。

Am I doing something wrong or how can I work around this problem? 我是在做错什么还是该如何解决这个问题?

  1. Method Dispatcher.Invoke blocks working thread until UI thread makes all updates 方法Dispatcher.Invoke阻止工作线程,直到UI线程进行所有更新

  2. UI thread uses some resources locked by working thread (through RaiseCanExecuteChanged -> CanExecute method chain in the above code) and blocks UI线程使用了工作线程锁定的一些资源(通过上面的代码中的RaiseCanExecuteChanged > CanExecute方法链)和块

  3. Deadlock since worker thread waits for UI thread to finish update and UI thread waits worker thread to release locked resources 死锁,因为工作线程等待UI线程完成更新,而UI线程等待工作线程释放锁定的资源

A possible way to ensure no deadlocks is to asynchronously invoke updates on UI thread using Dispatcher.BeginInvoke . 确保没有死锁的一种可能方法是使用Dispatcher.BeginInvoke 异步调用UI线程上的更新

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

This way UI thread will wait for a moment when working thread releases locked resources and then will update. 这样,当工作线程释放锁定的资源然后更新时,UI线程将等待片刻。 But there will be no deadlock. 但是不会有僵局。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM