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



我以前遇到过这个异常:

调用线程无法访问此对象,因为线程拥有它。

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

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中的示例,请参见BackgroundWorkerProgress<T>等类。如果您正在编写类似的类型,那么您可以在类实例化时(即在构造函数中)捕获Dispatcher对象。保存Dispatcher.CurrentDispatcher的值,并在需要调用Invoke()时使用保存的对象引用。


但实际上,这种情况非常罕见。大多数时候,编写的库代码对UI、线程、Dispatcher或任何其他与UI相关的东西都没有依赖性。在更常见的情况下,正确的做法是不要在库中做任何事情。相反,如果需要,让库的客户端代码来处理它。

这样做简化了库,并允许它在任何不同的场景中使用,无论是否使用Dispatcher。当然,当您将责任留给UI对象时,它具有您习惯使用的Dispatcher属性,因此您不需要乱用例如Application.Current.Dispatcher

相关内容

最新更新