gpt4 book ai didi

c# - Prism 5 DelegateCommandBase.RaiseCanExecuteChanged 抛出 InvalidOperationException

转载 作者:太空狗 更新时间:2023-10-29 23:30:57 26 4
gpt4 key购买 nike

我刚刚从 Prism 4.1 更新到 5,以前工作正常的代码现在抛出 InvalidOperationExceptions。我怀疑根本原因是更新的异步 DelegateCommand 没有正确编码到 UI 线程。

我需要能够从任何线程调用 command.RaiseCanExecuteChanged() 并为此引发 UI 线程上的 CanExecuteChanged 事件。 Prism 文档说这就是 RaiseCanExecuteChanged() 方法应该做的。但是,随着 Prism 5 更新,这不再有效。 CanExecuteChanged 事件在非 UI 线程上被调用,并且在该非 UI 线程上访问 UI 元素时我得到下游 InvalidOperationExceptions。

这是提供解决方案提示的 Prism 文档:

DelegateCommand includes support for async handlers and has been moved to the Prism.Mvvm portable class library. DelegateCommand and CompositeCommand both use the WeakEventHandlerManager to raise the CanExecuteChanged event. The WeakEventHandlerManager must be first constructed on the UI thread to properly acquire a reference to the UI thread’s 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);
}

这是异常堆栈:

Test method Calypso.Pharos.Commands.Test.PatientSessionCommandsTests.Fails threw exception: System.InvalidOperationException: The calling thread cannot access this object because a different thread owns it. at System.Windows.Threading.Dispatcher.VerifyAccess() at System.Windows.DependencyObject.GetValue(DependencyProperty dp) at System.Windows.Controls.Primitives.ButtonBase.get_Command() at System.Windows.Controls.Primitives.ButtonBase.UpdateCanExecute() at System.Windows.Controls.Primitives.ButtonBase.OnCanExecuteChanged(Object sender, EventArgs e) at System.Windows.Input.CanExecuteChangedEventManager.HandlerSink.OnCanExecuteChanged(Object sender, EventArgs e) at Microsoft.Practices.Prism.Commands.WeakEventHandlerManager.CallHandler(Object sender, EventHandler eventHandler) at Microsoft.Practices.Prism.Commands.WeakEventHandlerManager.CallWeakReferenceHandlers(Object sender, List`1 handlers) at Microsoft.Practices.Prism.Commands.DelegateCommandBase.OnCanExecuteChanged() at Microsoft.Practices.Prism.Commands.DelegateCommandBase.RaiseCanExecuteChanged() at Calypso.Pharos.Commands.Test.PatientSessionCommandsTests.<>c__DisplayClass10.b__e() in PatientSessionCommandsTests.cs: line 71 at System.Threading.Tasks.Task.InnerInvoke() at System.Threading.Tasks.Task.Execute() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter.GetResult() at Calypso.Pharos.Commands.Test.PatientSessionCommandsTests.d__12.MoveNext() in PatientSessionCommandsTests.cs: line 71 --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter.GetResult()

最佳答案

我不知道你是否还需要答案,但也许有人会观察到同样的错误。

所以问题是,正如您正确提到的那样,RaiseCanExecuteChanged()方法并不总是将事件处理程序调用发布到 UI 线程的同步上下文。

如果我们看一下 WeakEventHandlerManager实现,我们看到两件事。首先,这个静态类有一个私有(private)静态字段:

private static readonly SynchronizationContext syncContext = SynchronizationContext.Current;

其次,有一个私有(private)方法,它应该使用这个同步上下文并将事件处理程序调用实际发布到该上下文:

    private static void CallHandler(object sender, EventHandler eventHandler)
{
if (eventHandler != null)
{
if (syncContext != null)
{
syncContext.Post((o) => eventHandler(sender, EventArgs.Empty), null);
}
else
{
eventHandler(sender, EventArgs.Empty);
}
}
}

所以,看起来还不错,但是...

正如我之前所说,这种调用发布“并不总是”发生。 “不总是”是指,例如,这种情况:

  • 您的程序集已在发布配置中构建并启用了优化
  • 您没有将调试器附加到程序集

在这种情况下,.NET 框架优化了代码执行,现在重要的是,可以初始化静态 syncContext在任何时候但在第一次使用之前。所以,这发生在我们的例子中——只有当你第一次调用 CallHandler() 时,这个字段才会被初始化。方法(当然是间接调用 RaiseCanExecuteChanged() )。因为你可以从线程池调用这个方法,在这种情况下没有同步上下文,所以该字段将被设置为 nullCallHandler()方法在当前线程上调用事件处理程序,而不是在 UI 线程上。

在我看来,对此的解决方案是黑客攻击或某种代码味道。反正我不喜欢。您应该只确保 CallHandler()第一次从 UI 线程调用,例如,通过调用 RaiseCanExecuteChanged() DelegateCommand 上的方法具有有效 CanExecuteChanged 的实例事件订阅。

希望这对您有所帮助。

关于c# - Prism 5 DelegateCommandBase.RaiseCanExecuteChanged 抛出 InvalidOperationException,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25751465/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com