C# 多线程——无控件调用
我对多线程只是有点熟悉,因为我读过它,但从未在实践中使用过它。
我有一个项目使用第三方库,通过引发事件来共享输入设备的状态。问题是,库的编写方式是从不同的线程引发这些事件。
我的应用程序不需要是多线程的,而且我遇到了很多经典的线程问题(UI 控件抱怨与不同线程进行交互,当一段代码迭代时集合被修改,等等) .)。
我只想将第 3 方库的事件返回给我的 UI 线程。具体来说,我认为应该发生的是:
我的类接收事件,并且处理程序在与 UI 不同的线程上运行。我想检测这种情况(就像使用 InvokeRequired 一样),然后执行相当于 BeginInvoke 的操作,将控制权交还给 UI 线程。然后,可以在类层次结构上发送适当的通知,并且我的所有数据仅由一个线程触及。
问题是,接收这些输入事件的类不是从 Control 派生的,因此没有 InvokeRequired 或 BeginInvoke。这样做的原因是我试图将 UI 和底层逻辑彻底分开。该类仍在 UI 线程上运行,只是类本身内部没有任何 UI。
现在我通过破坏这种分离来解决这个问题。我传入对将显示我的类中的数据并使用其 Invoke 方法的控件的引用。这似乎违背了分离它们的全部目的,因为现在底层类直接依赖于我的特定 UI 类。
也许有一种方法可以保存对运行构造函数的线程的引用,然后 Threading 命名空间中有一些东西可以执行 Invoke 命令?
有办法解决这个问题吗?我的做法完全错误吗?
I am only somewhat familiar with multi-threading in that I've read about it but have never used it in practice.
I have a project that uses a third party library that shares the status of an input device by raising events. The problem is, the way the library is written these events are raised from a different thread.
My application does not need to be multi-threaded and I've run into a lot of classic threading issues (UI controls complaining about being interacted with from a different thread, collections that get modified as one piece of code is iterating over it, etc.).
I just want the 3rd party library's event to be given back to my UI thread. Specifically what I think should happen is:
My class receives the event and the handler is being run on a different thread than the UI. I want to detect this condition (like with InvokeRequired), and then perform the equivalent of BeginInvoke to give control back to the UI thread. Then the proper notifications can be sent on up the class hierarchy and all of my data is only touched by the one thread.
The problem is, the class that is receiving these input events is not derived from Control and therefore doesn't have InvokeRequired or BeginInvoke. The reason for this is that I tried to cleanly separate UI and the underlying logic. The class is still being run on the UI thread, it just doesn't have any UI inside the class itself.
Right now I fixed the issue by ruining that separation. I pass in a reference to the control that will be displaying data from my class and using its Invoke methods. That seems like it defeats the whole purpose of separating them because now the underlying class has a direct dependence on my specific UI class.
Perhaps there's a way to save a reference to the thread that ran the constructor and then there's something in the Threading namespace that will perform the Invoke commands?
Is there a way around this? Is my approach completely wrong?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
查看
AsyncOperation
班级。您可以在要使用AsyncOperationManager.CreateOperation
方法。我用于Create
的参数通常为 null,但您可以将其设置为任何值。要调用该线程上的方法,请使用AsyncOperation.Post
方法。Look into the
AsyncOperation
class. You create an instance ofAsyncOperation
on the thread you want to call the handler on using theAsyncOperationManager.CreateOperation
method. The argument I use forCreate
is usually null, but you can set it to anything. To call a method on that thread, use theAsyncOperation.Post
method.使用 SynchronizationContext.Current,它将指向您可以与之同步的东西。
根据应用程序的类型,这将做正确的事情™。对于 WinForms 应用程序,它将在主 UI 线程上运行。
具体来说,使用 SynchronizationContext.Send 方法,像这样:
Use SynchronizationContext.Current, which will point to something that you can synchronize with.
This will do the right thing™ depending on the type of application. For a WinForms application, it will run this on the main UI thread.
Specifically, use the SynchronizationContext.Send method, like this:
处理方法可以简单地将数据存储到类的成员变量中。当您想要将线程更新为不是在该线程上下文中创建的控件时,就会出现跨线程的唯一问题。因此,您的泛型类可以侦听该事件,然后使用委托函数调用您想要更新的实际控件。
同样,只有您想要更新的 UI 控件才需要被调用,以确保它们是线程安全的。不久前,我在“C# 中非法跨线程调用的简单解决方案"
这篇文章介绍了更多细节,但一个非常简单(但有限)方法的关键是在您的 UI 控件上使用匿名委托函数想要更新:
我希望这有帮助。 InvokeRequired 在技术上是可选的,但调用控件的成本相当高,因此检查确保在不需要时不会通过调用更新 label1.Text。
The handling method could simply store the data into a member variable of the class. The only issue with cross-threading occurs when you want to update threads to controls not created in that thread context. So, your generic class could listen to the event, and then invoke the actual control you want to update with a delegate function.
Again, only the UI controls that you want to update need to be invoked to make them thread safe. A while ago I wrote a blog entry on a "Simple Solution to Illegal Cross-thread Calls in C#"
The post goes into more detail, but the crux of a very simple (but limited) approach is by using an anonymous delegate function on the UI control you want to update:
I hope this helps. The InvokeRequired is technically optional, but Invoking controls is quite costly, so that check ensure it doesn't update label1.Text through the invoke if its not needed.
您不需要特定的控件,任何控件(包括表单)都可以。所以你可以将它从 UI 中抽象出来。
You don't need the specific control, any control (including the Form) will do. So you could abstract it away from the UI somewhat.
如果您使用的是 WPF:
您需要对管理 UI 线程的 Dispatcher 对象的引用。然后,您可以使用调度程序对象上的 Invoke 或 BeginInvoke 方法来安排在 UI 线程中发生的操作。
获取调度程序的最简单方法是使用 Application.Current.Dispatcher。这是负责主(也可能是唯一)UI 线程的调度程序。
把它们放在一起:
If you are using WPF:
You need a reference to the Dispatcher object which manages the UI thread. Then you can use the Invoke or BeginInvoke method on the dispatcher object to schedule an operation which takes place in the UI thread.
The simplest way to get the dispatcher is using Application.Current.Dispatcher. This is the dispatcher responsible for the main (and probably the only) UI thread.
Putting it all together:
我刚刚遇到了同样的情况。但是,就我而言,我无法使用 SynchronizationContext.Current,因为我无权访问任何 UI 组件,也没有回调来捕获当前同步上下文。事实证明,如果代码当前未在 Windows 窗体消息泵中运行,SynchronizationContext.Current 将设置为标准 SynchronizationContext,它将仅在当前线程上运行 Send 调用,并在 ThreadPool 上运行 Post 调用。
我发现这个答案解释了不同类型的同步上下文。就我而言,解决方案是在线程上创建一个新的 WindowsFormsSynchronizationContext 对象,该对象稍后将使用 Application.Run() 启动消息泵。然后,其他线程可以使用此同步上下文在 UI 线程上运行代码,而无需接触任何 UI 组件。
I just ran into the same situation. However, in my case I couldn't use SynchronizationContext.Current, because I did not have access to any UI components and no callback to capture the current synchronization context. It turns out that if the code is not currently running in a Windows Forms messge pump, SynchronizationContext.Current will be set to a standard SynchronizationContext, which will just run Send calls on the current thread and Post calls on the ThreadPool.
I found this answer explaining the different types of synchronization contexts. In my case the solution was to create a new WindowsFormsSynchronizationContext object on the thread that would later start the message pump using Application.Run(). This synchronization context can then be used by other threads to run code on UI thread, without ever touching any UI components.
是的,解决方法是创建一个线程安全队列。
在任何一种情况下,因为您的队列是由两个不同的线程写入(第 3 方线程正在排队,而您的线程正在出队),所以它需要是一个线程安全的受保护队列。
Yes, the work-around would be for you to create a thread-safe queue.
In either case, because your queue is being written by two different threads (the 3rd-party thread is enqueueing, and your thread is dequeueing), it needs to be a thread-safe, protected queue.