如何避免 WPF 应用程序调用的库中出现 "different thread owns it" 异常?

How can I avoid "different thread owns it" exceptions in a library called by a WPF app?

我以前遇到过这个异常:

The calling thread cannot access this object because a different thread owns it.

通常是由具有异步方法的事件处理程序引起的。为了解决这个问题,在过去,通常我所要做的就是改变这样的东西:

myObject.CustomEvent += MyCustomEventHandler;

像这样:

myObject.CustomEvent += (s, e) => Dispatcher.Invoke(() => MyCustomEventHandler(s, e));

当所有这些代码都在同一个 WPF 应用程序中时,这一切都很好。但是,我的解决方案分为多个项目,其中一个项目是一个通用 "Utilities" 库,其中包含一些我经常使用的常用功能。在这个库中,我有一个特殊的 "Timer" class,它是用于定期执行给定方法的包装器。

所以它的代码看起来像这样:

timer.Elapsed += OnTimedEvent;

我试着把它改成这样,就像我习惯做的那样:

timer.Elapsed += (s, e) => Dispatcher.Invoke(() => OnTimedEvent(s, e));

但是,那不会编译。它说:

Cannot access non-static method 'Invoke' in a static context

所以,我认为 Dispatcher 在某种程度上是 WPF 应用程序内部当前调度程序的别名,但不是吗? (或类似的东西?)所以我的下一次尝试是将代码更改为:

timer.Elapsed += (s, e) => Dispatcher.CurrentDispatcher.Invoke(() => OnTimedEvent(s, e));

谢天谢地,这确实编译通过了。但是,它不能解决我的问题。我仍然看到那些讨厌的 InvalidOperationExceptions 和以前一样的消息:

The calling thread cannot access this object because a different thread owns it.

所以我通过添加 CurrentDispatcher 成功地完成了任何事情。我必须承认,当涉及到这些线程的一些东西时,我有一点知识空白,所以非常感谢您提供的任何建议!

做什么取决于您的图书馆,但在大多数情况下,正确的解决方案是什么都不做,而不是在图书馆本身。


在某些时候,如果您收到该异常,则会调用某些 UI 对象来处理该事件。 UI 对象应该关注调用 Dispatcher.Invoke().

请注意 Dispatcher 不是 "alias"。在您之前使用它的上下文中,它是您在其中编写代码的对象的 成员

当前线程的静态Dispatcher.CurrentDispatcher属性returnsDispatcher对象。当然,如果代码是 运行 在你想要调用操作的 UI 线程之外的线程中,那就是错误的 Dispatcher 对象。

但这确实提出了一种代码方法,您的库在某种程度上特别旨在处理跨线程调用问题。有关 .NET 中的示例,请参阅 class 等 BackgroundWorkerProgress<T>。如果您正在编写类似的类型,那么您可以在实例化 class 时捕获 Dispatcher 对象(即在构造函数中)。保存 Dispatcher.CurrentDispatcher 的值,并在需要调用 Invoke().

时使用保存的对象引用


但实际上,这种情况非常罕见。大多数时候,一个人编写的库代码对 UI、它的线程、它的 Dispatcher 或任何其他 UI 相关的东西都是零依赖的。在这种更常见的情况下,正确的做法是不要在图书馆本身做任何事情。相反,如有必要,让库的客户端代码处理它。

这样做可以简化库,并允许它在任何情况下使用,有或没有 Dispatcher。当然,当你把责任留给 UI 对象时,它有你习惯使用的 Dispatcher 属性,所以你不需要乱搞,例如Application.Current.Dispatcher.