我刚刚从Prism 4.1升级到5,之前可以正常工作的代码现在会抛出InvalidOperationExceptions异常。我怀疑导致问题的根本原因是更新后的异步DelegateCommands不能正确地转移到UI线程。
我需要能够从任何线程调用command.RaiseCanExecuteChanged()方法,并使其在UI线程上引发CanExecuteChanged事件。Prism文档说这就是RaiseCanExecuteChanged()方法应该做的事情。然而,使用Prism 5更新后,这不再起作用。CanExecuteChanged事件在非UI线程上被调用,当在此非UI线程上访问UI元素时,我会得到下游的InvalidOperationExceptions异常。
这里是Prism文档提供的一个解决方案的提示:
DelegateCommand包括对异步处理程序的支持,并已移动到Prism.Mvvm可移植类库中。DelegateCommand和CompositeCommand都使用WeakEventHandlerManager来引发CanExecuteChanged事件。必须在UI线程上首先构造WeakEventHandlerManager以正确获取UI线程的SynchronizationContext的引用。
但是,WeakEventHandlerManager是静态的,所以我无法构造它...
有人知道我如何按照Prism文档在UI线程上构造WeakEventHandlerManager吗?
这里是一个无法通过的单元测试,可以重现这个问题:
[TestMethod]
public async Task Fails()
{
bool canExecute = false;
var command = new DelegateCommand(() => Console.WriteLine(@"Execute"),
() =>
{
Console.WriteLine(@"CanExecute");
return canExecute;
});
var button = new Button();
button.Command = command;
Assert.IsFalse(button.IsEnabled);
canExecute = true;
// Calling RaiseCanExecuteChanged from a threadpool thread kills the test
// command.RaiseCanExecuteChanged(); works fine...
await Task.Run(() => command.RaiseCanExecuteChanged());
Assert.IsTrue(button.IsEnabled);
}
以下是异常堆栈:
测试方法Calypso.Pharos.Commands.Test.PatientSessionCommandsTests.Fails引发异常:System.InvalidOperationException:调用线程无法访问此对象,因为不同的线程拥有它。 在System.Windows.Threading.Dispatcher.VerifyAccess()中 在System.Windows.DependencyObject.GetValue(DependencyProperty dp)中 在System.Windows.Controls.Primitives.ButtonBase.get_Command()中 在System.Windows.Controls.Primitives.ButtonBase.UpdateCanExecute()中 在System.Windows.Controls.Primitives.ButtonBase.OnCanExecuteChanged(Object sender, EventArgs e)中 在System.Windows.Input.CanExecuteChangedEventManager.HandlerSink.OnCanExecuteChanged(Object sender, EventArgs e)中 在Microsoft.Practices.Prism.Commands.WeakEventHandlerManager.CallHandler(Object sender, EventHandler eventHandler)中 在Microsoft.Practices.Prism.Commands.WeakEventHandlerManager.CallWeakReferenceHandlers(Object sender, List`1 handlers)中 在Microsoft.Practices.Prism.Commands.DelegateCommandBase.OnCanExecuteChanged()中 在Microsoft.Practices.Prism.Commands.DelegateCommandBase.RaiseCanExecuteChanged()中 在Calypso.Pharos.Commands.Test.PatientSessionCommandsTests.<>c__DisplayClass10.b__e()中,在PatientSessionCommandsTests.cs第71行 在System.Threading.Tasks.Task.InnerInvoke()中 在System.Threading.Tasks.Task.Execute()中 --- 异常堆栈跟踪结束于前一次引发异常的位置 --- 在System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)中 在System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)中 在System.Runtime.CompilerServices.TaskAwaiter.GetResult()中 在Calypso.Pharos.Commands.Test.PatientSessionCommandsTests.d__12.MoveNext()中,在PatientSessionCommandsTests.cs第71行 --- 异常堆栈跟踪结束于前一次引发异常的位置 --- 在System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)中 在System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)中 在System.Runtime.CompilerServices.TaskAwaiter.GetResult()中