2013-09-06 2 views
2

Я использую WPF и DelegateCommand из PRISM и имеют следующую проблему:RaiseCanExecuteChanged вызывается, когда ждут причины тупиковой

Я начала операции асинхронной как:

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, но он отлично работает).

Я делаю что-то неправильно или как я могу обойти эту проблему?

+2

Что делает ваш поток пользовательского интерфейса (т. Е. Какой стек вызовов), когда происходит тупик? – svick

+0

@svick: метод 'ProgramDevice' ожидает ожидаемого« ожидания », и обработчик события входит в' Invoke' и зависает в 'RaiseCanExecuteChanged'. Стек вызовов кажется прекрасным (DownloadFirmwareAsync -> OnIsProgrammedChanged -> AnonymousMethod) ... – ChrFin

+0

@chrfin: Пожалуйста, разместите весь стек вызовов. –

ответ

0

Нашли проблему:
Это был не RaiseCanExecuteChanged, но фактическое CanExecute, срабатывающий им. Там у меня был AsyncLock, который ждал завершения задачи программирования, прежде чем возвращать значение, которое я использую, чтобы удалить, если UiCommand может быть выполнен -> тупик, так как задание программирования вызвало его ...

Я решил это просто используя свойство «sync» (которое не использует блокировку и просто возвращает текущее значение/stat) значения, которое мне нужно.

1

Я вижу, что вы уже решили свою проблему, но я подумал, что дам более общее решение, которое поможет вам предотвратить подобные взаимоблокировки в будущем.

В вашем случае, вы могли бы легко избежать этого тупика с помощью ConfigureAwait вроде этого:

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

Что это делает позволяет продолжение будет выполняться в другом потоке, чем оригинал. Это не всегда возможно, так как много раз вам нужно продолжение, которое нужно выполнить в потоке пользовательского интерфейса, но для этого вопроса я не считаю, что это так. Поэтому в основном лучше всего использовать ConfigureAwait(false), если вам не нужно возобновлять выполнение из исходной темы.

This article подробно объясняет, почему происходят эти тупики и как их избежать. Другое рекомендуемое чтение: Best Practices in Asynchronous Programming.

+0

Спасибо за ввод, но я уже знал это, и я использую 'ConfigureAwait (false)', где это возможно. В этом случае это невозможно, так как метод 'ProgramDevice' обращается к пользовательскому интерфейсу после вызова' async', который я оставил здесь, чтобы сохранить код коротким. – ChrFin

0

Я делаю что-то неправильно или как я могу обойти эту проблему?

  1. Метод Dispatcher.Invoke блокирует рабочий поток до тех пор, поток пользовательского интерфейса не делает все обновления

  2. потока пользовательского интерфейса использует некоторые ресурсы, заблокированные работает нить (через RaiseCanExecuteChanged ->CanExecute метод цепи в коде выше) и блоки

  3. Тупик с рабочего потока ждет завершения потока пользовательского интерфейса, а поток пользовательского интерфейса ожидает рабочий поток для освобождения заблокированных ресурсов

Возможный способ, чтобы убедиться в отсутствии тупиков является асинхронно вызывающих обновлений на UI потоке использованием Dispatcher.BeginInvoke.

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

В этом случае поток пользовательского интерфейса будет ждать момента, когда рабочий поток освободит заблокированные ресурсы, а затем обновится. Но не будет тупика.

Смежные вопросы