如何让一个类的多个实例(在不同的线程中)监听同一个事件
本文关键字:线程 监听 事件 同一个 实例 一个 | 更新日期: 2023-09-27 18:17:00
我是c#中事件处理和线程的新手,所以请原谅我,如果这个问题是基本的:我如何创建几个类在不同的线程上运行,所有监听相同的事件?
例如,我经常以随机的间隔接收到一条新的数据。当这些数据到达时,我用新数据更新一个类,我们称它为MyDataClass
,它引发一个事件:MyDataClass.NewEvent
。
然后我有一个叫做NewEventHandler
的课。当事件触发时,该类对新数据进行一些计算,然后将结果发送给另一个应用程序。
问题是这样的:我需要大约30个NewEventHandler
实例都监听MyDataClass.NewEvent
(每个实例进行不同的计算并产生不同的结果)。重要的是,这些计算都是同时运行的——一旦事件触发,所有30个NewEventHandler
实例都开始计算。然而,它们是否一起完成并不重要(例如,这里不需要同步)。
如何创建NewEventHandler
的实例,使它们在不同的线程上运行,并使它们都侦听MyDataClass.NewEvent
的单个实例?
在c#中,通常的做法是在触发事件的同一个线程中调用事件侦听器的方法。从源类触发事件的标准模板是:
void FireNewEvent()
{
var tmp = NewEvent;
if( tmp != null )
{
tmp(this, YourEventArgsInstance);
}
}
Event是一个美化了的委托。因此,这个调用类似于多播委托调用——这意味着所有订阅者都将在运行FireNewEvent
的同一线程上被顺序调用。我建议你不要改变这种行为。
如果要同时运行事件订阅者,则在每个订阅者中启动一个新任务。
...
MyDataClass.NewEvent += OneOfSubscriberClassInstance.OnNewEvent;
...
public void OnNewEvent(object sender, YourEventArgs args)
{
Task.Factory.StartNew( () => {
// all your event handling code here
});
}
触发事件的代码将依次触发30个订阅者,但每个订阅者将在TPL调度的自己的线程中运行。因此,触发事件的委托不必等待触发下一个订阅者的处理程序,直到当前调用的订阅者的处理程序完成对事件的处理。
下面是一个示例/演示如何同步不同的线程并确保它们同时响应事件。您可以将此代码复制并粘贴到控制台应用程序中,以查看它的运行情况。
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);
}
}