如何让一个类的多个实例(在不同的线程中)监听同一个事件?

Posted

技术标签:

【中文标题】如何让一个类的多个实例(在不同的线程中)监听同一个事件?【英文标题】:How to get multiple instances of a class (in different threads) to listen to the same event? 【发布时间】:2013-06-01 13:30:14 【问题描述】:

我是 C# 中的事件处理和线程的新手,如果这个问题是基本的,请原谅我:如何创建多个类以在不同的线程上运行,所有类都侦听相同的事件?

例如,我经常收到一条新数据,但时间间隔是随机的。当这些数据到达时,我用新数据更新一个类,我们称之为MyDataClass,它会引发一个事件:MyDataClass.NewEvent

然后我有一个名为NewEventHandler 的类。当事件触发时,该类对新数据进行一些计算,然后将结果发送到另一个应用程序。

所以问题是这样的: 我需要有大约 30 个 NewEventHandler 实例都在听 MyDataClass.NewEvent (每个都进行不同的计算并产生不同的结果)。重要的是,这些计算都同时运行 - 一旦事件触发所有 30 个 NewEventHandler 实例开始计算。但是,它们是否一起完成并不重要(例如,这里不需要同步)。

我如何实际创建NewEventHandler 的实例,以便它们在不同的线程上运行,并让它们都监听MyDataClass.NewEvent 的单个实例?

【问题讨论】:

Multiple threads waiting on one event?的可能重复 它可能是重复的,但我喜欢这个问题的抽象性质。 “多个线程等待一个事件?”具体阅读了那个 OP 域问题。我希望这个问题能得到自己的回答。 @Eric 问题是不同的 - 这个问题是关于处理 C# 样式事件(委托),建议的问题是关于阻止操作系统事件的同步。 请注意,类的实例不会“在不同的线程上运行”,它们根本不会运行。只有特定的方法(或者在 async/await 代码的情况下甚至是方法的一部分)可以说“在线程上运行”。 我要指出,事件(正如问题所提出的那样)实际上是在触发它的同一线程上调用的委托,因此另一个线程并没有真正监听事件.最简单的方法是设置 ManualResetEvent 而不是触发 C# 事件。 【参考方案1】:

在 C# 中,一般做法是在触发事件的同一线程上调用事件侦听器的方法。从源类触发事件的标准模板是:

void FireNewEvent() 

     var tmp = NewEvent;
     if( tmp != null )
      
         tmp(this, YourEventArgsInstance);
     

事件并不多,但有点荣耀的代表。所以这个调用类似于多播委托调用——这意味着所有订阅者将在FireNewEvent 正在运行的同一线程上按顺序调用。我建议你不要改变这种行为。

如果您想同时运行事件订阅者,那么您可以在每个订阅者中启动一个新任务。

...
MyDataClass.NewEvent += OneOfSubscriberClassInstance.OnNewEvent;

...
public void OnNewEvent(object sender, YourEventArgs args)
 
   Task.Factory.StartNew( () => 

       // all your event handling code here 
   );

触发事件的代码将依次触发 30 个订阅者,但每个订阅者将在 TPL 安排的自己的线程中运行。因此,触发事件的委托将不必等到当前调用的订阅者处理程序完成事件处理后再触发下一个订阅者的处理程序。

【讨论】:

+1 看起来 MyDataClass 应该有一些方法(用于任务)来启动一个新线程。 谢谢 - 我以前没有看过任务库,这看起来做得很好!提出了一整套新问题,但对于任何阅读此问题的人来说,MSDN page on Task Parallelism 看起来很冗长且易于理解【参考方案2】:

这是一个示例/演示,说明如何同步不同线程并确保它们都同时响应事件。您可以将此代码复制并粘贴到控制台应用程序中以查看它的运行情况。

public class Program

    private static EventWaitHandle _waitHandle;
    private const int ThreadCount = 20;
    private static int _signalledCount = 0;
    private static int _invokedCount = 0;
    private static int _eventCapturedCount = 0;
    private static CountdownEvent _startCounter;
    private static CountdownEvent _invokeCounter;
    private static CountdownEvent _eventCaptured;


    public static void Main(string[] args)
    
        _waitHandle = new EventWaitHandle(false, EventResetMode.ManualReset);
        _startCounter = new CountdownEvent(ThreadCount);
        _invokeCounter = new CountdownEvent(ThreadCount);
        _eventCaptured = new CountdownEvent(ThreadCount);

        //Start multiple threads that block until signalled
        for (int i = 1; i <= ThreadCount; i++)
        
            var t = new Thread(new ParameterizedThreadStart(ThreadProc));
            t.Start(i);
        

        //Allow all threads to start
        Thread.Sleep(100);

        _startCounter.Wait();

        Console.WriteLine("Press ENTER to allow waiting threads to proceed.");
        Console.ReadLine();

        //Signal threads to start
        _waitHandle.Set();

        //Wait for all threads to acknowledge start
        _invokeCounter.Wait();

        //Signal threads to proceed
        _waitHandle.Reset();

        Console.WriteLine("All threads ready. Raising event.");

        var me = new object();

        //Raise the event
        MyEvent(me, new EventArgs());

        //Wait for all threads to capture event
        _eventCaptured.Wait();
        Console.WriteLine("0 of 1 threads responded to event.", _eventCapturedCount, ThreadCount);
        Console.ReadLine();
    

    public static EventHandler MyEvent;

    public static void ThreadProc(object index)
    
        //Signal main thread that this thread has started
        _startCounter.Signal();
        Interlocked.Increment(ref _signalledCount);

        //Subscribe to event
        MyEvent += delegate(object sender, EventArgs args) 
         
            Console.WriteLine("Thread 0 responded to event.", index);
            _eventCaptured.Signal();
            Interlocked.Increment(ref _eventCapturedCount);
        ;

        Console.WriteLine("Thread 0 blocks.", index);

        //Wait for main thread to signal ok to start
        _waitHandle.WaitOne();

        //Signal main thread that this thread has been invoked
        _invokeCounter.Signal();
        Interlocked.Increment(ref _invokedCount);
    

【讨论】:

以上是关于如何让一个类的多个实例(在不同的线程中)监听同一个事件?的主要内容,如果未能解决你的问题,请参考以下文章

java - 如何在Java中运行类的不同实例的线程之间同步静态变量?

PHP在单例模式中,是多个线程只有一个实例还是一个线程只有一个实例

使用 TPL 在多个不同线程上创建单例对象

kafka分片和消费组的概念

可重入与线程安全(大多数Qt类是可重入,非线程安全的)

如何跨多个视图控制器引用同一个类的实例?