Search code examples
wpfasynchronousmvvmicommandcatel

InvalidOperationException when using a command on a second UI thread


We are adding a second UI thread to our catel 4.4.0 MVVM application.

I have created my view and view model on the second UI thread and am using a DispatcherTimer to update the view model periodically. That part seems to be working fine. However, when I create a Catel.MVVM.Command in the constructor of my view model I am running into issues. When the CanExecute of the command fires I am getting a System.InvalidOperationException on my second UI thread.

The command is nothing special.

private bool ImageClickCanExecute()
{
    return true;
}

public void ImageClickAction()
{
    if(ImageClickTarget != null)
        Process.Start(ImageClickTarget.ToString());
}

The view is not either.

<Grid>
    <Button Command="{Binding ImageClickCommand}">
        <Image Name="ImageSource" Source="{Binding ImageSource, FallbackValue={x:Null}}"/>
    </Button>
</Grid>

The stack trace is below.

0:010> !PrintException /d 00000282cb8effd0
Exception object: 00000282cb8effd0
Exception type:   System.Reflection.TargetInvocationException
Message:          Exception has been thrown by the target of an invocation.
InnerException:   System.InvalidOperationException, Use !PrintException 00000282cb8edee8 to see more.
StackTrace (generated):
    SP               IP               Function
    000000B0459FD510 0000000000000000 mscorlib_ni!System.RuntimeMethodHandle.InvokeMethod(System.Object, System.Object[], System.Signature, Boolean)+0x1
    000000B0459FD510 00007FFFCC5E1C20 mscorlib_ni!System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(System.Object, System.Object[], System.Object[])+0x80
    000000B0459FD580 00007FFFCC5BE789 mscorlib_ni!System.Delegate.DynamicInvokeImpl(System.Object[])+0x99
    000000B0459FD5D0 00007FFF70FC83AE UNKNOWN!Catel.EventHandlerExtensions.SplitInvoke(System.Delegate, System.Object[])+0xfe
    000000B0459FD630 00007FFF7164E085 UNKNOWN!Catel.EventHandlerExtensions.SafeInvoke(System.EventHandler, System.Object, System.EventArgs)+0x55
    000000B0459FD680 00007FFF71674998 UNKNOWN!Catel.MVVM.Command`2+<<RaiseCanExecuteChanged>b__32_0>d[[System.__Canon, mscorlib],[System.__Canon, mscorlib]].MoveNext()+0x38
    000000B0459FD7B0 00007FFFCD2E1224 mscorlib_ni!System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(System.Threading.Tasks.Task)+0xd3f9c4
    000000B0459FD7F0 00007FFFCC5A17AD mscorlib_ni!System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(System.Threading.Tasks.Task)+0x3d
    000000B0459FD820 00007FFF71674515 UNKNOWN!Catel.MVVM.Command`2+<>c__DisplayClass35_0+<<AutoDispatchIfRequiredAsync>b__0>d[[System.__Canon, mscorlib],[System.__Canon, mscorlib]].MoveNext()+0xf5
    000000B0459FD9B0 00007FFFCCFCDA88 mscorlib_ni!System.Runtime.CompilerServices.AsyncMethodBuilderCore+<>c.<ThrowAsync>b__6_0(System.Object)+0x38
    000000B0459FD9E0 00007FFFA8119C0E WindowsBase_ni!System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate, System.Object, Int32)+0xae
    000000B0459FDA50 00007FFFA8119AC6 WindowsBase_ni!System.Windows.Threading.ExceptionWrapper.TryCatchWhen(System.Object, System.Delegate, System.Object, Int32, System.Delegate)+0x36
    000000B0459FDAA0 00007FFFA811CA2B WindowsBase_ni!System.Windows.Threading.DispatcherOperation.InvokeImpl()+0x10b
    000000B0459FDB20 00007FFFCC5CA79E mscorlib_ni!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)+0x15e
    000000B0459FDBF0 00007FFFCC5CA637 mscorlib_ni!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)+0x17
    000000B0459FDC20 00007FFFCC5CA5F2 mscorlib_ni!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)+0x52
    000000B0459FDC70 00007FFFA8333810 WindowsBase_ni!MS.Internal.CulturePreservingExecutionContext.Run(MS.Internal.CulturePreservingExecutionContext, System.Threading.ContextCallback, System.Object)+0x80
    000000B0459FDCD0 00007FFFA811C784 WindowsBase_ni!System.Windows.Threading.DispatcherOperation.Invoke()+0x64
    000000B0459FDD30 00007FFFA8117C24 WindowsBase_ni!System.Windows.Threading.Dispatcher.ProcessQueue()+0x1a4
    000000B0459FDDB0 00007FFFA8118061 WindowsBase_ni!System.Windows.Threading.Dispatcher.WndProcHook(IntPtr, Int32, IntPtr, IntPtr, Boolean ByRef)+0x71
    000000B0459FDE30 00007FFFA8119E53 WindowsBase_ni!MS.Win32.HwndWrapper.WndProc(IntPtr, Int32, IntPtr, IntPtr, Boolean ByRef)+0xc3
    000000B0459FDEC0 00007FFFA8119D82 WindowsBase_ni!MS.Win32.HwndSubclass.DispatcherCallbackOperation(System.Object)+0x82
    000000B0459FDF10 00007FFFA8119BC9 WindowsBase_ni!System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate, System.Object, Int32)+0x69
    000000B0459FDF80 00007FFFA8119AC6 WindowsBase_ni!System.Windows.Threading.ExceptionWrapper.TryCatchWhen(System.Object, System.Delegate, System.Object, Int32, System.Delegate)+0x36
    000000B0459FDFD0 00007FFFA8117583 WindowsBase_ni!System.Windows.Threading.Dispatcher.LegacyInvokeImpl(System.Windows.Threading.DispatcherPriority, System.TimeSpan, System.Delegate, System.Object, Int32)+0x173
    000000B0459FE070 00007FFFA81194FF WindowsBase_ni!MS.Win32.HwndSubclass.SubclassWndProc(IntPtr, Int32, IntPtr, IntPtr)+0x11f
    000000B0459FE390 0000000000000000 WindowsBase_ni!MS.Win32.UnsafeNativeMethods.DispatchMessage(System.Windows.Interop.MSG ByRef)+0x1
    000000B0459FE450 00007FFFA812D8FC WindowsBase_ni!System.Windows.Threading.Dispatcher.PushFrameImpl(System.Windows.Threading.DispatcherFrame)+0xec
    000000B0459FE4E0 00007FFFA26A98B3 PresentationFramework_ni!System.Windows.Application.RunDispatcher(System.Object)+0x73
    000000B0459FE520 00007FFFA26A969D PresentationFramework_ni!System.Windows.Application.RunInternal(System.Windows.Window)+0x8d
    000000B0459FE580 00007FFF70FD70F7 UNKNOWN!xxx.Wpf.App.Main()+0x67
    000000B0459FF0C0 0000000000000000 mscorlib_ni!System.AppDomain._nExecuteAssembly(System.Reflection.RuntimeAssembly, System.String[])+0x1
    000000B0459FF0C0 00007FFFCCD57EBF mscorlib_ni!System.AppDomain.ExecuteAssembly(System.String, System.Security.Policy.Evidence, System.String[])+0x7f
    000000B0459FF110 00007FFF7053434E UNKNOWN!Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()+0x3e
    000000B0459FF150 00007FFFCC5CA79E mscorlib_ni!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)+0x15e
    000000B0459FF220 00007FFFCC5CA637 mscorlib_ni!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)+0x17
    000000B0459FF250 00007FFFCC5CA5F2 mscorlib_ni!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)+0x52
    000000B0459FF2A0 00007FFFCC51BEF2 mscorlib_ni!System.Threading.ThreadHelper.ThreadStart()+0x52

StackTraceString: <none>
HResult: 80131604
0:010> !PrintException /d 00000282cb8edee8
Exception object: 00000282cb8edee8
Exception type:   System.InvalidOperationException
Message:          The calling thread cannot access this object because a different thread owns it.
InnerException:   <none>
StackTrace (generated):
    SP               IP               Function
    000000B0459FCD70 00007FFFA8446977 WindowsBase_ni!System.Windows.Threading.Dispatcher.VerifyAccess()+0x32faf7
    000000B0459FCDA0 00007FFFA8112806 WindowsBase_ni!System.Windows.DependencyObject.GetValue(System.Windows.DependencyProperty)+0x26
    000000B0459FCE00 00007FFFA27EBB5B PresentationFramework_ni!System.Windows.Controls.Primitives.ButtonBase.get_Command()+0x2b
    000000B0459FCE30 00007FFFA27EBB00 PresentationFramework_ni!System.Windows.Controls.Primitives.ButtonBase.UpdateCanExecute()+0x10

StackTraceString: <none>
HResult: 80131509

Solution

  • This is because, by default, the commands automatically dispatch change notifications using the DispatcherService. Since you are running an edge-case scenario (2 ui threads), I recommend that you turn off the automatic dispatching and take care of this yourself.

    Since this is a static protected member, you will need to derive your own command class from CommandBase and set AutomaticallyDispatchEvents to false.

    ps. I recommend using the IProcessService instead of directly using Process.Start, it allows you to mock it or replace it if you need to.