如何从工作线程/类更新GUI线程/类?
第一个问题就到这里,大家好。
我正在处理的要求是一个小型测试应用程序,通过串行端口与外部设备进行通信。通信可能需要很长时间,并且设备可能会返回各种错误。
该设备很好地抽象在自己的类中,GUI线程开始在自己的线程中运行,并具有通常的打开/关闭/读取数据/写入数据的基本功能。 GUI 也非常简单 - 选择 COM 端口、打开、关闭、显示从设备读取的数据或错误、允许修改和写回等。
问题只是如何从设备类更新 GUI?设备处理多种不同类型的数据,因此我需要在 GUI 表单/线程类和工作设备类/线程之间建立一个相对通用的桥梁。在 GUI 到设备的方向上,一切都可以通过 [Begin]Invoke 调用在各种 GUI 生成的事件上进行打开/关闭/读/写等操作正常进行。
我已阅读线程 此处(如何从另一个更新 GUI C# 中的线程?) 其中假设 GUI 和工作线程位于同一类中。谷歌搜索提出了如何创建委托或如何创建经典的后台工作人员,但这根本不是我需要的,尽管它们可能是解决方案的一部分。那么,有没有一个简单但通用的结构可以使用呢?
我的 C# 水平中等,我的整个工作生涯都在编程,如果有线索我会弄清楚(并回发)...提前感谢您的帮助。
First question here so hello everyone.
The requirement I'm working on is a small test application that communicates with an external device over a serial port. The communication can take a long time, and the device can return all sorts of errors.
The device is nicely abstracted in its own class that the GUI thread starts to run in its own thread and has the usual open/close/read data/write data basic functions. The GUI is also pretty simple - choose COM port, open, close, show data read or errors from device, allow modification and write back etc.
The question is simply how to update the GUI from the device class? There are several distinct types of data the device deals with so I need a relatively generic bridge between the GUI form/thread class and the working device class/thread. In the GUI to device direction everything works fine with [Begin]Invoke calls for open/close/read/write etc. on various GUI generated events.
I've read the thread here (How to update GUI from another thread in C#?) where the assumption is made that the GUI and worker thread are in the same class. Google searches throw up how to create a delegate or how to create the classic background worker but that's not at all what I need, although they may be part of the solution. So, is there a simple but generic structure that can be used?
My level of C# is moderate and I've been programming all my working life, given a clue I'll figure it out (and post back)... Thanks in advance for any help.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
您可以在 UI 类上公开一个公共方法,设备类可以在后台线程上调用该方法,并将其传递到 UI 所需的所有信息包含在内。该公共方法将在后台线程的上下文中执行,但由于它属于 UI 类,因此您现在可以使用您已阅读过的任何调用封送技术。
因此,最简单的设计是:
MyUIForm
)添加一个名为UpdateUI()
的方法,该方法采用您正在使用的任何数据结构将数据从设备传递到您使用的 UI。如果您想稍后支持 DI/IoC,并让表单实现它,您可以在接口(例如IUIForm
)中声明该方法。MyUIForm.UpdateUI()
(或IUIForm.UpdateUI()
)。UpdateUI
根据需要执行Invoke
或BeginInvoke
。请注意,这样做的另一个好处是将所有 UI 和表示逻辑封装在 UI 类中。您的设备类现在可以专注于处理硬件。
更新:为了解决您的可扩展性问题 -
无论您的应用程序增长了多少以及您拥有多少 UI 类,您仍然希望使用您想要更新的特定 UI 类的 BeginInvoke 来跨越线程边界。 (该 UI 类可能是特定控件或特定可视化树的根,这并不重要)主要原因是,如果您有多个 UI 线程,则必须确保任何 UI 的更新都发生在该线程上由于 Windows 消息传递和窗口的工作方式,此特定 UI 是在其上创建的。因此,实际的跨线程边界逻辑应该封装在UI层。
您的设备类不必关心哪些 UI 类以及哪个线程需要更新。事实上,我个人会让设备完全不了解任何 UI,而只是公开不同 UI 类可以订阅的事件。
请注意,替代解决方案是将线程完全封装在设备类中,并使 UI 不知道空闲线程的存在。但是,线程边界交叉将成为设备类的责任,并且应包含在其逻辑中,因此您不应该使用跨线程的 UI 方式。这也意味着您的设备类绑定到特定的 UI 线程。
You can expose a public method on your UI class that the device class can call on the background thread with all the information it needs to pass to the UI. That public method will be executed in the context of the background thread, but since it belongs to the UI class, you can now employ any of the call marshaling techniques you've read about.
Thus, the simplest design then would be:
MyUIForm
)called something likeUpdateUI()
that takes whatever data structure you are using to pass the data from the device to the UI you use. You can declare that method in an interface (for exampleIUIForm
), if you want to support DI/IoC later, and have the form implement it.MyUIForm.UpdateUI()
(orIUIForm.UpdateUI()
).UpdateUI
doesInvoke
orBeginInvoke
as appropriate.Note that has the side benefit of encapsulating all the UI and presentation logic in your UI class. Your device class can now focus on dealing with the hardware.
Update: To address your scalability concerns -
No matter how much your app grows and how many UI classes you have, you still want to cross the thread boundary using the BeginInvoke for the particular UI class you want to update. (That UI class might be a specific control or the root of a particular visual tree, it does not really matter) The main reason is if you have more than one UI threads, you have to ensure the update of any UI happens on the thread this particualr UI was created on, due to the way Windows messaging and windows work. Hence, the actual logic of crossing the boundary thread should be encapsulated in the UI layer.
You device class should not have to care what UI classes and on which thread need to be updated. In fact, I personally would make the device fully ignorant about any UI and would just expose events on it that different UI classes can subscribe on.
Note that the alternative solution is to make the threading fully encapsulated in the device class and make the UI ignorant about the existence of a bacground thread. However, then thread boundary crossing then becomes responsibility of the device class and should be contained within its logic, so you shouldn't be using the UI way of crossing the threads. That also means your device class is bound to particular UI thread.
这是带有事件处理程序的版本。
它经过简化,因此表单中没有 UI 控件,SerialIoEventArgs 类中也没有属性。
This is a version with an event handler.
It is simplified so there is no UI controls in the form and no properties in the SerialIoEventArgs class.
因此,根据上述答案进行一些研究后,进一步 Google 搜索并询问一位对 C# 了解一点的同事,我选择的问题解决方案如下。我仍然对评论、建议和改进感兴趣。
首先是关于这个问题的一些进一步的细节,这实际上是相当通用的,因为 GUI 正在控制一些东西,这些东西必须保持完全抽象,通过 GUI 必须对其响应的一系列事件做出反应。有一些明显的问题:
第一部分是事件。由于 GUI 和设备可以引发多个事件,并且可能具有与之关联的不同数据类型,因此事件调度程序非常方便。这在事件和数据中都必须是通用的,因此:
现在我们需要一些来自 C 世界的事件,我喜欢枚举,所以我定义了 GUI 将引发的一些事件:
现在范围(通常是命名空间)内的任何位置EventDispatcher 的静态类可以定义一个新的调度程序:
这为枚举中的每个事件创建一个事件处理程序。
并通过订阅事件来消费,就像消费 Device 对象的构造函数中的代码一样:
其中 InControlThread.Invoke 是一个抽象类,它简单地包装了调用调用。
GUI 可以简单地引发事件:
这样做的优点是,如果事件引发和使用类型不匹配(此处为字符串文件名),编译器会发出错误消息。
可以进行许多改进,但这就是问题的核心。正如我在评论中所说,我很感兴趣,特别是如果有任何明显的遗漏/错误或缺陷。希望这对某人有帮助。
So, after some research based on the answers above, further Google searching and asking a colleague who knows a bit about C# my chosen solution to the problem is below. I remain interested in comments, suggestions and refinements.
First some further detail about the problem, which is actually pretty generic in the sense that the GUI is controlling something, that must remain wholly abstract, through a series of events to whose responses the GUI must react. There a a few distinct problems:
The first part of this is the events. As the GUI and the device can raise several events, possibly having different data types associated with them, an event dispatcher is handy. This must be generic in both events and data, so:
Now we need some events and coming from the C world, I like enums, so I define some events that the GUI will raise:
Now anywhere within the scope (namespace, typically) of the static class of the EventDispatcher it is possible to define a new dispatcher:
This creates an event handler for each event in the enum.
And consumed by subscribing to the event like this code in the constructor of the consuming Device object:
Where the InControlThread.Invoke is an abstract class that simply wraps the invoke call.
Events can be raised by the GUI simply:
This has the advantage that should the event raising and consuming types not match (here the string Filename) the compiler will grumble.
There are many enhancements that can be made but this is the nuts of the problem. I'd be interested, as I said in comments, especially if there are any glaring omissions/bugs or deficiencies. Hope this helps someone.