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

时间:2015-07-30 19:40:21

标签: c# multithreading

我有一个处理由多个成员对象创建的事件的类。来自这些对象的事件为该事件生成工作线程,以便我的类中的各种事件处理程序在不同的线程上运行(一个是串行处理程序,一个是计时器事件等)我正在寻找一种简单的方法使我的代码是线程安全的,最好是强制事件处理程序在我的对象的线程上运行。

如果这是一个Forms UI对象,我可以利用它ISynchronizeInvoke接口的实现,并调用InvokeRequiredInvoke等。在WPF我可以使用一个Dispatcher个对象。但我的课程需要独立于任何UI代码运行*。

以下是我所拥有的简化示例:

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
    }
}

因为两个事件处理程序都访问父类中的相同对象(包括彼此!),如果有一种简单的方法可以强制事件处理程序在创建对象的线程中运行,那就太棒了。

我已经玩弄了我的类实现ISynchronizeInvoke接口的想法,但看起来这样做会变得非常复杂。在我跳下那个兔子洞之前,我想我会告诉专家,看看是否有更简单的解决方案。

思想?

编辑:

我想在父对象的线程中运行事件处理程序的部分原因是因为父对象具有它自己的事件,这些事件是根据其成员对象发送的事件触发的。我希望这个类隐藏任何线程功能,因此使用该类的代码不必担心线程相关的问题(即锁等)。简单地锁定共享数据将无法完成工作,因为我仍然需要从线程事件处理程序中触发事件。

4 个答案:

答案 0 :(得分:2)

在另一个线程上调用的ideea与一个while循环不同,它会不时地检查是否有一个" outside"要处理的消息。对于UI,有一个Windows循环来做到这一点。对于外部线程,您必须手动编写循环。想象一下没有循环的情况,你有一个相对长的运行线程吗?并且你想要中断这个线程来调用你的消息并恢复它在同一个共享堆栈内存上做的事情。这种中断会破坏你的筹码。这根本不可能。另一种可能性是使用诸如ManualResetEvent之类的同步机制,只是等待信号(来自线程外部的信号)。因此,要恢复,为了处理来自另一个线程的消息,您基本上只有两个选项:

1)你有一个while循环,最终使用一点睡眠(给其他线程一些时间/滴答作用)

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

2)你只是等待以某种方式实现生产者/消费者体系结构的消息:

On listening thread:
aManualResetEvent.WaitOne ();

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

.NET框架中有一些高级类可能对BlockingCollection有帮助。

希望这有帮助

答案 1 :(得分:2)

假设您的类在其自己的线程中运行,唯一的逻辑是执行来自其他线程的incomming调用,这将是解决方案: (内部评论)

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
        }
    }
}

基于调用者线程调用函数的解决方案:ThreadedEventHandler1和ThreadedEventHandler2,实际上将真正的调用添加到队列中并立即继续运行。

另一方面,StartConsume函数迭代队列并调用添加的方法调用。如果要在调用前后添加其他逻辑,可以将其添加到此函数中。

希望它有助于实现你的目标。

答案 2 :(得分:0)

没有完全理解你的设计背后的理性。我可以说你试图解决的问题之前已经解决过很多次了。

我将假设您的主对象就像一个服务,它希望从自身和其他服务(子对象)调用(在本例中为事件)。如果您在服务方面考虑它(您可以说应该这样),WCF解决了这个问题,因为你做了所有繁重的工作@Rami建议。

您可以使用以下行为定义主服务:
实例上下文模式 - 单个
并发模式 - 单一 有关这些here的更多信息。

每个事件处理程序都会调用该主服务通知它事件。

我很确定你不会那么做并实施every class as a service,但认为无论如何都值得提供。

答案 3 :(得分:0)

好的,基于你的所有反馈(谢谢!)我有一个解决我的问题的方法。简短的回答:我想做的事情是不可能的。

以下是有关提问者的详细信息。我正在编写一个DLL来管理连接到串行端口的设备。这包括基本串行端口COM(数据包TX和RX,包括解析)和更高级别的协议行为(TX,Ack,超时重试等).NET提供的串行端口事件处理程序显然是异步的,因为它们是我用来处理超时等的System.Timers.Timer对象

我正在围绕MVVM架构构建代码,因此我的UI在其中没有任何逻辑。因此,我需要避免利用UI提供的DispatcherInvoke功能。

我正在寻找的是一种以WinForms和WPF提供的相同简单方式处理DLL中的异步事件的方法。但正如已经指出的那样,正如我在深入挖掘时所了解到的那样,当你调用BeginInvokeDispatcher时,你正在做的是将某些东西推到队列中,以后再用不同的东西消耗线程轮询队列。在UI的上下文之外,不存在这样的轮询架构。

SO。我的选择是lock我的类中的共享对象使其线程安全,或者在另一个线程中实现我自己的轮询架构(以避免阻止使用DLL的程序)模拟UI代码已经执行的操作

在任何一种情况下,UI代码在处理DLL类中的事件时仍需要使用其Invoke或等效工具。我想那没关系。