简体   繁体   English

强制事件处理程序在对象的线程C#.NET上运行

[英]Force event handler to run on object's thread, C# .NET

I have a class that handles events created by multiple member objects. 我有一个处理由多个成员对象创建的事件的类。 Events from these objects spawn worker threads for the event, so that the various event handlers in my class are running on different threads (one is a serial handler, one is a timer event, etc.) I'm looking for a simple way to make my code thread-safe, preferably by forcing the event handlers to run on my object's thread. 这些对象产生的事件会为该事件生成工作线程,以便我类中的各种事件处理程序在不同的线程上运行(一个是串行处理程序,一个是计时器事件,等等。)我正在寻找一种简单的方法来使我的代码具有线程安全性,最好是强制事件处理程序在对象的线程上运行。

If this were a Forms UI object, I could take advantage of its implementation of the ISynchronizeInvoke interface, and make calls to InvokeRequired , Invoke , etc. In WPF I could use a Dispatcher object. 如果这是一个Forms UI对象,则可以利用其对ISynchronizeInvoke接口的实现,并调用InvokeRequiredInvoke等。在WPF中,我可以使用Dispatcher对象。 But my class needs to run *independently of any UI code. 但是我的课程需要*独立于任何UI代码运行。

Here's a simplified example of what I have: 这是我所拥有的简化示例:

public class MyClass
{
    private SomeObject object1;
    private AnotherObject object2;

    public MyClass()
    {
        object1 = new SomeObject();
        object2 = new AnotherObject();

        object1.AThreadedEvent += ThreadedEventHandler1;
        object2.AnotherThreadedEvent += ThreadedEventHandler2;
    }

    // This runs in its own thread!
    private void ThreadedEventHandler1()
    {
        // DO STUFF HERE
    }

    // This runs in its own thread!
    private void ThreadedEventHandler2()
    {
        // DO STUFF HERE
    }
}

Because both event handlers access the same objects in the parent class (including each-other!), it would be awesome if there were a simple way to force the event handlers to run in the creating object's thread. 因为两个事件处理程序都访问父类中的相同对象(包括彼此!),所以如果有一种简单的方法来强制事件处理程序在创建对象的线程中运行,那就太好了。

I've toyed with the idea of having my class implement the ISynchronizeInvoke interface, but it appears that doing so can get pretty complicated. 我曾想过让类实现ISynchronizeInvoke接口,但是这样做似乎ISynchronizeInvoke复杂。 Before I jump down that rabbit hole, I thought I'd ping the experts to see if there is a more simple solution. 在跳下兔子洞之前,我曾想过要对专家进行检查,看看是否有更简单的解决方案。

Thoughts? 有什么想法吗?

EDIT: 编辑:

Part of the reason I want to run the event handlers in the parent object's thread is because the parent object has it's *own events that are triggered based on the events sent by its member objects. 我想要在父对象的线程中运行事件处理程序的部分原因是因为父对象具有*自己的事件,这些事件是根据其成员对象发送的事件触发的。 I'd like any threading functionality to be hidden by this class, so that code that uses the class doesn't have to worry about thread-related issues (ie. locks and so on). 我希望此类可以隐藏任何线程功能,因此使用该类的代码不必担心与线程相关的问题(例如,锁等)。 Simply locking shared data won't do the job, because I *still need to trigger events from within the threaded event handlers. 简单地锁定共享数据将无法完成任务,因为我仍然需要从线程事件处理程序中触发事件。

The ideea of invoking on another thread is hand to hand with having a while loop that from time to time it checks whether there is an "outside" message to be processed. 调用另一个线程的想法与一个while循环密切相关,该循环不时检查是否有“外部”消息要处理。 For UI, there is the windows loop that does that. 对于UI,有执行此操作的Windows循环。 For an external thread, you must write manually a loop. 对于外部线程,必须手动编写循环。 Imagine a situation without a loop and that you have a relative long running thread right ? 想象一下没有循环的情况,并且您有一个运行时间相对较长的线程,对吗? and sudently you want to interrupt this thread to invoke your message and resume what it was doing ON THE SAME shared stack memory. 并且突然想中断该线程以调用您的消息并在相同的共享堆栈内存上继续执行该操作。 This interruption would destroy your stack. 这种中断会破坏您的堆栈。 This is simply NOT possible. 这根本不可能。 The other possibility is to use a synchronization mechanism such as ManualResetEvent and just wait for a signal (a signal that comes outside your thread). 另一种可能性是使用诸如ManualResetEvent之类的同步机制,只等待一个信号(线程外部的信号)。 So, to resume, in order to process a message from another thread, you basically have only two options: 因此,要恢复,为了处理来自另一个线程的消息,您基本上只有两个选择:

1) You have a while loop, eventually using a little sleep (to give some time / ticks to other threads to do their job) 1)您有一个while循环,最终花了一点时间(给其他线程一些时间/滴答声来完成工作)

while (true) {
  Thread.Sleep (5);
  if (someMessageArrived) { ... }
}

2) You just wait for a message implementing somehow the producer / consummer architecture: 2)您只需要等待消息以某种方式实现生产者/消费者体系结构:

On listening thread:
aManualResetEvent.WaitOne ();

On the "producer" thread:
aManualResetEvent.Set ();

There are advanced classes in .NET framework that might help such as BlockingCollection. .NET框架中有一些高级类可能会有所帮助,例如BlockingCollection。

Hope this helps 希望这可以帮助

Assumming, that your class runs in its own thread that the only logic is to execute the incomming calls from other threads, this would be the solution: (comments inside) 假设您的类在自己的线程中运行,唯一的逻辑是执行来自其他线程的传入调用,这将是解决方案:(内部的注释)

public class MyClass
{
    private SomeObject object1;
    private AnotherObject object2;

    public MyClass()
    {
        object1 = new SomeObject();
        object2 = new AnotherObject();

        object1.AThreadedEvent += ThreadedEventHandler1;
        object2.AnotherThreadedEvent += ThreadedEventHandler2;
    }

    // This runs in its own thread!
    // Only add the real function call to the queue
    public void ThreadedEventHandler1()
    {
        tasks.Add(ThreadedEventHandler1_really);
    }

    private void ThreadedEventHandler1_really()
    {
        // DO STUFF HERE
    }

    // This runs in its own thread!
    // Only add the real function call to the queue
    public void ThreadedEventHandler2()
    {
        tasks.Add(ThreadedEventHandler2_really);
    }

    // here is the actual logic of your function
    private void ThreadedEventHandler2_really()
    {
        // DO STUFF HERE
    }

    // the queue of the tasks
    BlockingCollection<Action> tasks = new BlockingCollection<Action>();

    // this method never returns, it is blocked forever 
    // and the only purpose of i is to do the functions calls when they added to the queue
    // it is done in the thread of this instance
    public void StartConsume()
    {
        foreach (Action action in tasks.GetConsumingEnumerable())
        {
            // add logic before call
            action();
            // add logic after call
        }
    }
}

The solution based on that the caller threads tat calls the functions: ThreadedEventHandler1 and ThreadedEventHandler2, actually add the real call to the queue and emediately continue with their run. 基于调用者线程tat调用以下函数的解决方案:ThreadedEventHandler1和ThreadedEventHandler2,实际上将实际调用添加到队列中并立即继续其运行。

From the other hand, StartConsume function iterates the queue and makes the calls of the added method calls. 另一方面,StartConsume函数迭代队列并进行添加的方法调用的调用。 If you want to add another logic before and after call, you can add it in this function. 如果要在调用之前和之后添加其他逻辑,则可以在此函数中添加它。

Hope it helped to achieve your goal. 希望它有助于实现您的目标。

Without completely understanding the rational behind your design. 没有完全理解设计背后的原理。 I can say that the problem you are trying to solve was solved many times before. 我可以说您要解决的问题以前已经解决了很多次。

I will assume your main object is like a service which expects calls (in this case events) from itself and other services (the sub objects). 我将假设您的主要对象就像一个服务,期望它自身和其他服务(子对象)进行调用(在这种情况下为事件)。 If you would think about it in terms of services (which you arguably should) WCF solves that problem for you doing all the heavy lifting @Rami suggested. 如果您从服务方面考虑(您可能应该考虑),WCF会为您解决所有问题,从而解决@Rami建议的所有问题。

You define the main service with the following behavior: 您使用以下行为定义主要服务:
Instance Context Mode - Single 实例上下文模式-单
Concurrency Mode - Single 并发模式-单
More about these here . 更多关于这些在这里

And every event handler would call that main service notifying it about the event. 每个事件处理程序都会调用该主要服务,以通知该事件。

I am pretty sure you would not go that far and implement every class as a service , but thought it is worth offering anyway as an option. 我敢肯定,您不会走那么远的距离并将每个类都实现为服务 ,但是无论如何它还是值得提供的。

OK, based on all of your feedback (thanks!) I have a solution to my problem. 好的,根据您的所有反馈(谢谢!),我已经解决了我的问题。 The short answer: what I wanted to do isn't possible. 简短的答案:我想做的事是不可能的。

Here are more details for those who asked. 这是那些询问的人的更多详细信息。 I'm writing a DLL that manages a device attached to a serial port. 我正在写一个DLL,用于管理连接到串行端口的设备。 This includes basic serial port COM (packet TX and RX, including parsing), and higher-level protocol behavior (TX, Ack, retry on timeout, etc.) The serial port event handlers provided by .NET are obviously asynchronous, as are the System.Timers.Timer objects that I use to handle timeouts, etc. 这包括基本的串行端口COM(分组TX和RX,包括解析),以及更高级别的协议行为(TX,Ack,超时重试等)。.NET提供的串行端口事件处理程序显然是异步的,我用于处理超时等的System.Timers.Timer对象。

I am building the code around an MVVM architecture, so that my UI doesn't have any logic in it whatsoever. 我正在围绕MVVM架构构建代码,因此我的UI完全没有任何逻辑。 Hence my need to avoid exploiting Dispatcher or Invoke functionality provided by the UI. 因此,我需要避免利用UI提供的DispatcherInvoke功能。

What I was looking for was a way to handle asynchronous events within my DLL in the same simple manner provided by WinForms and WPF. 我一直在寻找一种以WinForms和WPF提供的简单方式来处理DLL中异步事件的方法。 But as has been pointed out, and as I learned when digging deeper, what you are *really doing when you call BeginInvoke or a Dispatcher is pushing something onto a queue, to be consumed later by a different thread polling the queue. 但是正如已经指出的那样,正如我在深入研究时所了解到的那样,当您调用BeginInvokeDispatcher将某些东西推入队列时,您实际上是在做什么,以后将由另一个查询该队列的线程来使用。 Outside the context of a UI, no such polling architecture exists. 在UI上下文之外,不存在这种轮询架构。

SO. 所以。 My options are to lock the shared objects in my class to make it thread safe, or to implement my own polling architecture within another thread (to avoid blocking the program that uses the DLL) that emulates what the UI code already does. 我的选择是lock的共享对象以使其成为线程安全,或在模仿UI代码已经执行的操作的另一个线程中实现我自己的轮询体系结构(以避免阻塞使用DLL的程序)。

In either case, the UI code will still need to use its Invoke or equivalent tools when handling events from the DLL class. 无论哪种情况,在处理DLL类中的事件时,UI代码仍将需要使用其Invoke或等效工具。 I suppose that's OK. 我想没关系。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM