当前位置: 首页 > 编程语言 > C#

Prism 5 DelegateCommandBase.RaiseCanExecuteChanged抛出InvalidOperationException分享

时间:2023-04-11 00:17:10 C#

C#学习教程:Prism5DelegateCommandBase.RaiseCanExecuteChanged抛出InvalidOperationException我怀疑根本原因是更新的异步DelegateCommand没有正确编组到UI线程。我需要能够从任何线程调用command.RaiseCanExecuteChanged()并在UI线程上引发CanExecuteChanged事件。Prism文档说这是RaiseCanExecuteChanged()方法应该做的。但是,随着Prism5的更新,它不再有效。CanExecuteChanged事件在非UI线程上调用,我得到下游InvalidOperationExceptions,因为在此非UI线程上访问UI元素。这是为解决方案提供提示的Prism文档:DelegateCommand包括对异步处理程序的支持,并且已移至Prism.Mvvm可移植类库。DelegateCommand和CompositeCommand都使用Wea??kEventHandlerManager引发CanExecuteChanged事件。必须首先在UI线程上构造WeakEventHandlerManager才能正确获取对UI线程的SynchronizationContext的引用。但是,WeakEventHandlerManager是静态的,所以我无法构造它...根据Prism文档,有人知道如何在UI线程上构造WeakEventHandlerManager吗?这是一个重现问题的失败单元测试:[TestMethod]publicasyncTaskFails(){boolcanExecute=false;varcommand=newDelegateCommand(()=>Console.WriteLine(@"Execute"),()=>{Console.WriteLine(@"CanExecute");returncanExecute;});变种按钮=新按钮();按钮。命令=命令;断言.IsFalse(按钮.IsEnabled);可以执行=真;//从线程池线程调用RaiseCanExecuteChanged会终止测试//command.RaiseCanExecuteChanged();工作正常...等待Task.Run(()=>command.RaiseCanExecuteChanged());Assert.IsTrue(按钮.IsEnabled);这是异常堆栈:测试方法Calypso.Pharos.Commands.Test.PatientSessionCommandsTests.Fails抛出异常:System.InvalidOperationException:调用线程无法访问此对象,因为另一个线程拥有它。System.Windows.Conters.Brton.ButtonBase.UpdateCanExecute中的System.Windows.Controls.Primitives.ButtonBase.get_Command()处的System.Windows.Threading.Dispatcher.VerifyAccess()处于System.Windows.DependencyObject.GetValue(DependencyPropertydp)处。()位于Microsoft.Practices.Prism.Commands.WeakEventHandlerManager的System.Windows.Input.CanExecuteChangedEventManager.HandlerSink.OnCanExecuteChanged(对象发送者,EventArgse)的System.Windows.Controls.Primitives.ButtonBase.OnCanExecuteChanged(对象发送者,EventArgse)中。Microsoft.Practices.Prism上Microsoft.Practices.Prism.Commands.DelegateCommandBase.OnCanExecuteChanged()的Microsoft.Practices.Prism.Commands.WeakEventHandlerManager.CallWeakReferenceHandlers(对象发送者,List`1处理程序)中的.CallHandler(对象发送者,EventHandlereventHandler)在PatientSessionCommandsTests.cs中的Calypso.Pharos.Commands.Test.PatientSessionCommandsTests。.Commands.DelegateCommandBase.RaiseCanExecuteChanged()ofc__DisplayClass10.b__e():m.Threading.Tasks.Task.InnerInvoke()inSystem.Threading.Tasks.Task.Execute()online71ofSyste-前一个抛出位置结束的异常堆栈跟踪—在System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(TaskTask)在System.Runtime.CompilerServices.TaskAwaiter.GetResult()在Patient.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(TaskTask)在PatientSessionCommandsTests。Calypso.Pharos.Commands.Test.PatientSessionCommandsTests.d__12.MoveNext()在cs中:第71行-从先前抛出异常的位置开始的堆栈跟踪结束-在System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(任务任务),在系统.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(TaskTask).Runtime.CompilerServices.TaskAwaiter.GetResult()我不知道你是否还需要答案,但也许有人会观察到同样的错误。所以问题是,正如您正确提到的那样,RaiseCanExecuteChanged()方法并不总是将事件处理程序调用发布到UI线程的同步上下文。如果我们查看WeakEventHandlerManager实现,我们会看到两件事。首先,这个静态类有一个私有静态字段:privatestaticreadonlySynchronizationContextsyncContext=SynchronizationContext.Current;其次,有一个私有方法,它应该使用这个同步上下文并实际发布对它的事件处理程序调用:{syncContext.Post((o)=>eventHandler(sender,EventArgs.Empty),null);}else{eventHandler(sender,EventArgs.Empty);所以,它看起来不错,但是......正如我之前所说,这个电话帖子“并不总是”。“不总是”意味着,例如,这种情况:在这种情况下,.NET框架优化了代码执行,现在重要的是静态syncContext字段可以在第一次使用之前随时初始化。那么在我们的例子中会发生什么——这个字段仅在您第一次调用CallHandler()方法时初始化(当然是通过调用RaiseCanExecuteChanged()间接调用)。并且因为你可以调用RaiseCanExecuteChanged()从线程池调用这个方法,在这种情况下没有同步上下文,所以该字段将被设置为null并且CallHandler()方法调用当前线程上的事件处理程序,但是不在UI线程上。从我的角度来看,解决方案是破解或某种代码味道。反正我不喜欢。您应该确保第一次从UI线程调用CallHandler(),例如,通过在具有对CanExecuteChanged事件的有效订阅的DelegateCommand实例上调用RaiseCanExecuteChanged()方法。希望这能有所帮助。单元测试确保您的功能在任何情况下代码更改后都不会中断,我见过不同的编写单元测试的方法,有些人编写单元测试是为了代码覆盖率。有些人只编写单元测试来满足他们的功能或业务需求。不管是什么,单元测试意味着您期望根据您的输入得到一些结果。我建议你不要在单元测试中引用UI组件,因为如果你将Button更改为其他控件,测试用例将无法工作,你不需要async和await修饰符。如有必要,您应该在DelegateCommand中使用异步和等待。Prism5支持这个,你可以在codeplex中查看源代码。每当您调用RaiseCanExecuteChanged时,它都会触发附加到DelegateCommand的CanExecute委托,并尝试禁用/启用UI控件。UI控件在UI线程中,但您的RaiseCanExecuteChanged在Worker线程中。通常这会破坏您的代码。我的建议是编写测试用例以期望下面的输出如果CanExecute方法返回true那么Execute方法应该触发如果CanExecute方法返回false那么CanExecute方法不应该触发[TestMethod]publicvoidFails(){boolisExecuted=false;布尔可以执行=假;varcommand=newDelegateCommand(()=>{Console.WriteLine(@"Execute");isExecuted=true;}()=>{Console.WriteLine(@"CanExecute");returncanExecute;});//执行前断言Assert.IsFalse(IsExecuted);命令.RaiseCanExecuteChanged();断言.IsFalse(IsExecuted);可以执行=真;断言.IsFalse(IsExecuted);测试总是执行断言来验证输出,所以不需要标记async和await测试方法以上是C#学习教程:Prism5DelegateCommandBase.RaiseCanExecuteChangedthrowsInvalidOperationException分享的所有内容,如果对大家有用,需要的话了解更多C#学习教程,希望大家多多关注—本文收集自网络,不代表立场。如涉及侵权,请点击右侧联系管理员删除。如需转载请注明出处: