3

The example below uses the DelegateCommand from Prism 6.1, but I've produced the same issue with 5.0.

Using the following view-model (view omitted, just consists of 2 buttons):

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();
            });
        });
    } 
}

If the ActiveCommand is called first, then this exception occurs:

An exception of type 'System.InvalidOperationException' occurred in WindowsBase.dll but was not handled in user code

Additional information: The calling thread cannot access this object because a different thread owns it.

This is, as far as I can tell, the standard "you're not allowed to talk to Wpf controls if you're not on the UI thread" exception. This seems at odds with the method summary:

Raises Prism.Commands.DelegateCommandBase.CanExecuteChanged on the UI thread so every command invoker can requery to check if the command can execute.

Also, I've had no problem calling this method from non-UI threads in the past.

Weirder still, if the TestCommand is raised first, then the ActionCommand starts working fine. I've checked and the code inside the Task.Run block is running on a non-UI thread in all cases.

Unfortunately, I can't use this as a work around in my real code - I've tried having the UI thread call a RaiseCanExecuteChanged before a worker thread does it, and it doesn't help.

Is there any reason for RaiseCanExecuteChanged to act this way? Any fix or workaround?

Community
  • 1
  • 1
Tony Harrison
  • 68
  • 2
  • 7
  • 1
    I think the documentation is wrong. Looking [at the source code](https://github.com/PrismLibrary/Prism/blob/master/Source/Prism/Commands/DelegateCommandBase.cs) I don't see any code that causes the event to be invoked on the UI thread. – Scott Chamberlain Nov 11 '15 at 22:11

1 Answers1

1

You're creating TestCommand on the UI thread and trying to access it on a separate thread. You can't do that. If all you want to do is raise the can execute, then just await Task.Run and then call it.

    public MainWindowViewModel()
    {
        TestCommand = new DelegateCommand(Test, CanTest);

        ActionCommand = new DelegateCommand(async () => 
        {
            await Task.Run(() =>
            {
                Thread.Sleep(1000);
            });

            TestCommand.RaiseCanExecuteChanged();
        });
    }
  • The author knows that, however his question is the fact that if you mouse over the `RaiseCanExecuteChanged` and look at the documentation associated with it it states that it is going to raise the event on the UI thread. – Scott Chamberlain Nov 11 '15 at 22:33
  • An issue has been submitted, and the XML comments will be clarified: https://github.com/PrismLibrary/Prism/issues/299 –  Nov 11 '15 at 22:46
  • Your example in the issue report (taking the OP's code) is not a good example. You need someone to register to the CanExceute event who throws a exception if raised from a non UI thread (The op has a view with two buttons he did not include the code for that fills this roll). The Delegate itself does not care which thread it is called on, it is the View the OP is binding to it from that is throwing the cross thread exception. – Scott Chamberlain Nov 11 '15 at 22:48
  • It is good enough to know that the docs should be updated to be more clear about the DelegateCommand not doing anything special to raise anything on the UI thread. –  Nov 12 '15 at 00:00
  • For knowledge purposes, this was supported back in Prism v2 with use of the Dispatcher, but was later removed in v2.2 and above. That is the source of the XML comments. They were never updated... like most docs :) –  Nov 12 '15 at 00:24
  • What's strange though is that sometimes it does work as the docs suggest - I've got production code calling the method from non-UI threads without the exception, but I can't replicate that behaviour in testing. – Tony Harrison Nov 12 '15 at 10:28
  • @TonyHarrison as I said in a earlier, the exception is not caused by the `DelegateCommand` but by the subscriber to the command who subscribed to the `CanExecuteChanged` event. So your testing likely does cause it to happen on the wrong thread but whatever you are using to test does not care which thread `CanExecuteChanged` was fired on. – Scott Chamberlain Nov 12 '15 at 14:32