Почему Prism DelegateCommands иногда вызывают исключения потоков?
В приведенном ниже примере используется DelegateCommand из Prism 6.1, но я создал ту же проблему с 5.0.
Используя следующую модель представления (представление опущено, просто состоит из 2 кнопок):
public class MainWindowViewModel
{
public DelegateCommand TestCommand { get; set; }
public DelegateCommand ActionCommand { get; set; }
public MainWindowViewModel()
{
TestCommand = new DelegateCommand(()=> TestCommand.RaiseCanExecuteChanged());
ActionCommand = new DelegateCommand(() =>
{
Task.Run(() =>
{
Thread.Sleep(1000);
TestCommand.RaiseCanExecuteChanged();
});
});
}
}
Если ActiveCommand вызывается первым, возникает это исключение:
Исключение типа "System.InvalidOperationException" произошло в WindowsBase.dll, но не было обработано в коде пользователя
Дополнительная информация: вызывающий поток не может получить доступ к этому объекту, так как он принадлежит другому потоку.
Насколько я могу судить, это стандартное исключение "вам не разрешено общаться с элементами управления Wpf, если вы не в потоке пользовательского интерфейса". Кажется, это расходится с описанием метода:
Вызывает Prism.Commands.DelegateCommandBase.CanExecuteChanged в потоке пользовательского интерфейса, чтобы каждый вызывающий команду мог запросить, может ли команда быть выполнена.
Кроме того, в прошлом у меня не было проблем с вызовом этого метода из потоков без пользовательского интерфейса.
Еще более странно, что если сначала создать TestCommand, то ActionCommand начнет работать нормально. Я проверил, и код внутри блока Task.Run во всех случаях выполняется в потоке без пользовательского интерфейса.
К сожалению, я не могу использовать это в качестве обходного пути в своем реальном коде - я пытался заставить поток пользовательского интерфейса вызывать RaiseCanExecuteChanged до того, как это сделает рабочий поток, и это не помогает.
Есть ли какая-либо причина для RaiseCanExecuteChanged действовать таким образом? Любое исправление или обходной путь?
1 ответ
Вы создаете TestCommand в потоке пользовательского интерфейса и пытаетесь получить к нему доступ в отдельном потоке. Вы не можете сделать это. Если все, что вы хотите сделать, это поднять команду can execute, просто дождитесь Task.Run и затем вызовите его.
public MainWindowViewModel()
{
TestCommand = new DelegateCommand(Test, CanTest);
ActionCommand = new DelegateCommand(async () =>
{
await Task.Run(() =>
{
Thread.Sleep(1000);
});
TestCommand.RaiseCanExecuteChanged();
});
}