4

我知道,当您是调用者、调用方法或以其他方式操作控件时,您应该调用 UI 线程(或无论如何拥有该控件的线程)来执行此操作。

但是,当控件通过其事件之一被回调时,假设您在正确的线程上被调用是否安全?

根据我对常用控件的经验,这总是正确的,但也许这只是因为大多数事件是用户交互的结果,因此 Windows 消息由 UI 线程上的主消息循环处理。

最近我遇到了一个我自己的自定义控件的问题,它调用事件的原因不是响应用户交互,有时是在后台线程上调用的。在一种情况下,该事件的事件处理程序试图操纵另一个产生非法跨线程调用异常的控件。

我可以通过检查我的事件处理程序中是否需要调用来解决问题,但我很想知道谁在这里实际上是“有过错”的。

我在任何地方都找不到任何说明有关控件事件的任何“规则”甚至最佳实践的文档。有人知道吗?或者,在您看来,是否应该由控件负责在正确的线程上调用订阅者或订阅者负责检查?

编辑:似乎没有人听说过任何记录在案的约定,但普遍认为最好Control在控件所属线程的派生类上调用公共事件,以避免让消费者感到惊讶。

4

3 回答 3

3

我知道,当您是调用者、调用方法或以其他方式操作控件时,您应该调用 UI 线程(或无论如何拥有该控件的线程)来执行此操作。

嗯……值得商榷。当然,您永远不应该尝试从托管它的线程以外的线程访问任何 UI 元素。但是,使用编组技术Invoke并不总是更新 UI 元素的最佳机制,尤其是当您只想使用来自工作线程的进度信息更新 UI 时。别弄错我的意思。在某个时间和某个地方,使用封送操作非常有意义,但是很多时候让 UI 线程轮询它需要通过 a 进行自我更新的数据,这System.Windows.Forms.Timer使得解决方案通常更简单、更高效且同样优雅(如果不是更多)。

但是,当控件通过其事件之一被回调时,假设您在正确的线程上被调用是否安全?

不必要。我的意思是通常情况下,尤其是Control实例。但请记住,您也可以Component在表单上删除实例。其中许多在其他线程上引发事件。考虑BackgroundWorker.DoWorkSerialPort.DataReceived作为令人满意的反例。

我可以通过检查我的事件处理程序中是否需要调用来解决问题,但我很想知道谁在这里实际上是“有过错”的。

我不得不说,错在你。如果您的控件确实是子类Control,那么我会非常努力地确保在 UI 线程上引发所有事件。如果你不这样做,那么它肯定会让其他(正确或错误)假设他们在 UI 线程上的开发人员感到困惑。如果您的“控件”只有子类Component,那么您可能没问题,但请确保您记录了组件的行为。

于 2012-05-05T02:07:09.250 回答
3

避免在这里假设黑魔法在起作用,你描述的是理智的结果。如果您从工作线程引发事件,那么事件处理程序当然也会在该线程上运行。如果您在该处理程序中设置 UI 控件的属性,那么您将被提醒这不是合法的事情。

完全相同的机制在控件引发的事件中起作用。它们根据 Windows 发送的通知消息进行操作,因此这发生在泵送消息循环的线程上。总是你在 Winforms 或 WPF 应用程序中的主线程,除非你正在做一些不明智的事情,比如在工作线程上创建窗口。因此,这些事件在“正确”线程上引发,并且更新控件的属性不是问题。

事件不会从一个线程跳到另一个线程,除非您明确编写代码来这样做。Begin/Invoke(),你已经知道了。

并注意 ab/using InvokeRequired,它是一种反模式。事先不知道特定代码在哪个线程上运行是不健康的。假设您在工作人员上执行长时间运行的 dbase 查询以避免冻结 UI。因此,当它的结果可用时,您知道您将不得不调用以显示结果。使用 InvokeRequired 没有任何意义,只是它能够告诉您确实有问题。一定要在返回 false 时抛出异常。

于 2012-05-05T10:00:12.877 回答
0

在我看来,如果我是您的控件的消费者,并且使用它开始抛出线程异常,我可能会有点恼火:) 我想我会在检查被引发的事件的阵营,而不是取决于在订户上。

于 2012-05-05T02:00:30.650 回答