我以前遇到过这个异常:
调用线程无法访问此对象,因为线程拥有它。
通常是由具有异步方法的事件处理程序引起的。为了解决这个问题,在过去,通常我所要做的就是改变这样的东西:
myObject.CustomEvent += MyCustomEventHandler;
类似的东西:
myObject.CustomEvent += (s, e) => Dispatcher.Invoke(() => MyCustomEventHandler(s, e));
当所有这些代码都位于同一个WPF应用程序中时,这一切都很好。然而,我的解决方案分为多个项目,其中一个是通用的"实用程序"库,它有一些我经常使用的通用函数。在这个库中,我有一个特殊的"Timer"类,它是一个包装器,用于定期执行给定的方法。
所以它有这样的代码:
timer.Elapsed += OnTimedEvent;
我试着把它改成这个,就像我习惯做的那样:
timer.Elapsed += (s, e) => Dispatcher.Invoke(() => OnTimedEvent(s, e));
然而,这不会编译。上面写着:
无法访问静态上下文中的非静态方法"Invoke">
所以,我认为Dispatcher
在某种程度上是WPF应用程序中当前调度器的别名,但在其他方面不是吗?(或者类似的东西?)所以我的下一次尝试是将代码更改为:
timer.Elapsed += (s, e) => Dispatcher.CurrentDispatcher.Invoke(() => OnTimedEvent(s, e));
谢天谢地,它确实编译了。然而,这并不能解决我的问题。我仍然看到那些令人讨厌的InvalidOperationException,消息和以前一样:
调用线程无法访问此对象,因为线程拥有它。
因此,通过添加CurrentDispatcher,我什么也没成功。我必须承认,当涉及到这些线索时,我有一点知识差距,所以非常感谢你能提供的任何建议!
做什么取决于您的库,但在大多数情况下,正确的解决方案是什么都不做,而不是在库本身。
在某个时刻,如果您得到该异常,则会调用某个UI对象来处理该事件。UI对象本身应该关注调用Dispatcher.Invoke()
。
请注意,Dispatcher
不是"别名"。在您以前使用过它的上下文中,它是您编写代码的对象的成员。
静态Dispatcher.CurrentDispatcher
属性返回当前线程的Dispatcher
对象。当然,如果代码在要调用操作的UI线程之外的线程中运行,那么这就是错误的Dispatcher
对象。
但这确实为代码提供了一种方法,其中您的库在某种程度上是专门用于处理跨线程调用问题的。有关.NET中的示例,请参见BackgroundWorker
和Progress<T>
等类。如果您正在编写类似的类型,那么您可以在类实例化时(即在构造函数中)捕获Dispatcher
对象。保存Dispatcher.CurrentDispatcher
的值,并在需要调用Invoke()
时使用保存的对象引用。
但实际上,这种情况非常罕见。大多数时候,编写的库代码对UI、线程、Dispatcher
或任何其他与UI相关的东西都没有依赖性。在更常见的情况下,正确的做法是不要在库中做任何事情。相反,如果需要,让库的客户端代码来处理它。
这样做简化了库,并允许它在任何不同的场景中使用,无论是否使用Dispatcher
。当然,当您将责任留给UI对象时,它具有您习惯使用的Dispatcher
属性,因此您不需要乱用例如Application.Current.Dispatcher
。