Почему 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();
        });
    }
Другие вопросы по тегам