事件(event)


 

事件概述

委托是一种类型可以被实例化,而事件可以看作将多播委托进行封装的一个对象成员(简化委托调用列表增加和删除方法)但并非特殊的委托,保护订阅互不影响。

 

基础事件(event)

在.Net中声明事件使用关键词event,使用也非常简单在委托(delegate)前面加上event:

 1     class Program
 2     {
 3         /// <summary>
 4         /// 定义有参无返回值委托
 5         /// </summary>
 6         /// <param name="i"></param>
 7         public delegate void NoReturnWithParameters();
 8         /// <summary>
 9         /// 定义接受NoReturnWithParameters委托类型的事件
10         /// </summary>
11         static event NoReturnWithParameters NoReturnWithParametersEvent;
12         static void Main(string[] args)
13         {
14             //委托方法1
15             {
16                 Action action = new Action(() =>
17                 {
18                     Console.WriteLine("测试委托方法1成功");
19                 });
20                 NoReturnWithParameters noReturnWithParameters = new NoReturnWithParameters(action);
21                 //事件订阅委托
22                 NoReturnWithParametersEvent += noReturnWithParameters;
23                 //事件取阅委托
24                 NoReturnWithParametersEvent -= noReturnWithParameters;
25             }
26             //委托方法2
27             {
28                 //事件订阅委托
29                 NoReturnWithParametersEvent += new NoReturnWithParameters(() =>
30                 {
31                     Console.WriteLine("测试委托方法2成功");
32                 });
33             }
34             //委托方法3
35             {
36                 //事件订阅委托
37                 NoReturnWithParametersEvent += new NoReturnWithParameters(() => Console.WriteLine("测试委托方法3成功"));
38             }
39             //执行事件
40             NoReturnWithParametersEvent();
41             Console.ReadKey();
42         }
43         /*
44          * 作者:Jonins
45          * 出处:http://www.cnblogs.com/jonins/
46          */
47     }

上述代码执行结果:

 

事件发布&订阅

事件基于委托,为委托提供了一种发布/订阅机制。当使用事件时一般会出现两种角色:发行者订阅者。

发行者(Publisher)也称为发送者(sender):是包含委托字段的类,它决定何时调用委托广播。

订阅者(Subscriber)也称为接受者(recevier):是方法目标的接收者,通过在发行者的委托上调用+=和-=,决定何时开始和结束监听。一个订阅者不知道也不干涉其它的订阅者。

来电->打开手机->接电话,这样一个需求,模拟订阅发布机制:

 1     /// <summary>
 2     /// 发行者
 3     /// </summary>
 4     public class Publisher
 5     {
 6         /// <summary>
 7         /// 委托
 8         /// </summary>
 9         public delegate void Publication();
10 
11         /// <summary>
12         /// 事件  这里约束委托类型可以为内置委托Action
13         /// </summary>
14         public event Publication AfterPublication;
15         /// <summary>
16         /// 来电事件
17         /// </summary>
18         public void Call()
19         {
20             Console.WriteLine("显示来电");
21             if (AfterPublication != null)//如果调用列表不为空,触发事件
22             {
23                 AfterPublication();
24             }
25         }
26     }
27     /// <summary>
28     /// 订阅者
29     /// </summary>
30     public class Subscriber
31     {
32         /// <summary>
33         /// 订阅者事件处理方法
34         /// </summary>
35         public void Connect()
36         {
37             Console.WriteLine("通话接通");
38         }
39         /// <summary>
40         /// 订阅者事件处理方法
41         /// </summary>
42         public void Unlock()
43         {
44             Console.WriteLine("电话解锁");
45         }
46     }
47     /*
48      * 作者:Jonins
49      * 出处:http://www.cnblogs.com/jonins/
50      */
 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             //定义发行者
 6             Publisher publisher = new Publisher();
 7             //定义订阅者
 8             Subscriber subscriber = new Subscriber();
 9             //发行者订阅 当来电需要电话解锁
10             publisher.AfterPublication += new Publisher.Publication(subscriber.Unlock);
11             //发行者订阅 当来电则接通电话
12             publisher.AfterPublication += new Publisher.Publication(subscriber.Connect);
13             //来电话了
14             publisher.Call();
15             Console.ReadKey();
16         }
17     }

执行结果:

注意

1.事件只可以从声明它们的类中调用, 派生类无法直接调用基类中声明的事件。

1  publisher.AfterPublication();//这行代码在Publisher类外部调用则编译不通过

2.对于事件在声明类外部只能+=,-=不能直接调用,而委托在外部不仅可以使用+=,-=等运算符还可以直接调用。

下面调用方式与上面执行结果一样,利用了委托多播的特性。

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             Publisher publisher = new Publisher();
 6             Subscriber subscriber = new Subscriber();
 7             //------利用多播委托-------
 8             var publication = new Publisher.Publication(subscriber.Unlock);
 9             publication += new Publisher.Publication(subscriber.Connect);
10             publisher.AfterPublication += publication;
11             //---------End-----------
12             publisher.Call();
13             Console.ReadKey();
14         }
15     }

 

 自定义事件(EventArgs&EventHandler&事件监听器)

有过Windwos Form开发经验对下面的代码会熟悉:

1 private void Form1_Load(object sender, EventArgs e)
2 {
3      ...      
4 }

在设计器Form1.Designer.cs中有事件的附加。这种方式属于Visual Studio IDE事件订阅。

1  this.Load += new System.EventHandler(this.Form1_Load);

在 .NET Framework 类库中,事件基于 EventHandler 委托和 EventArgs 基类。

基于EventHandler模式的事件

 1     /// <summary>
 2     /// 事件监听器
 3     /// </summary>
 4     public class Consumer
 5     {
 6         private string _name;
 7 
 8         public Consumer(string name)
 9         {
10             _name = name;
11         }
12         public void Monitor(object sender, CustomEventArgs e)
13         {
14             Console.WriteLine($"Name:{_name}; 信息:{e.Message};到底要不要接呢?");
15         }
16     }
17     /// <summary>
18     /// 定义保存自定义事件信息的对象
19     /// </summary>
20     public class CustomEventArgs : EventArgs//作为事件的参数,必须派生自EventArgs基类
21     {
22         public CustomEventArgs(string message)
23         {
24             this.Message = message;
25         }
26         public string Message { get; set; }
27     }
28     /// <summary>
29     /// 发布者
30     /// </summary>
31     public class Publisher
32     {
33         public event EventHandler<CustomEventArgs> Publication;//定义事件
34         public void Call(string w)
35         {
36             Console.WriteLine("显示来电." + w);
37             OnRaiseCustomEvent(new CustomEventArgs(w));
38         }
39         //在一个受保护的虚拟方法中包装事件调用。
40         //允许派生类覆盖事件调用行为
41         protected virtual void OnRaiseCustomEvent(CustomEventArgs e)
42         {
43             //在空校验之后和事件引发之前。制作临时副本,以避免可能发生的事件。
44             EventHandler<CustomEventArgs> publication = Publication;
45             //如果没有订阅者,事件将是空的。
46             if (publication != null)
47             {
48                 publication(this, e);
49             }
50         }
51     }
52     /// <summary>
53     /// 订阅者
54     /// </summary>
55     public class Subscriber
56     {
57         private string Name;
58         public Subscriber(string name, Publisher pub)
59         {
60             Name = name;
61             //使用c# 2.0语法订阅事件
62             pub.Publication += UnlockEvent;
63             pub.Publication += ConnectEvent;
64         }
65         //定义当事件被提起时该采取什么行动。
66         void ConnectEvent(object sender, CustomEventArgs e)
67         {
68             Console.WriteLine("通话接通.{0}.{1}", e.Message, Name);
69         }
70         void UnlockEvent(object sender, CustomEventArgs e)
71         {
72             Console.WriteLine("电话解锁.{0}.{1}", e.Message, Name);
73         }
74     }
75     /*
76      * 作者:Jonins
77      * 出处:http://www.cnblogs.com/jonins/
78      */

调用方式:

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             Publisher pub = new Publisher();
 6             //加入一个事件监听
 7             Consumer jack = new Consumer("Jack");
 8             pub.Publication += jack.Monitor;
 9             Subscriber user1 = new Subscriber("中国移动", pub);
10             pub.Call("号码10086");
11             Console.WriteLine("--------------------------------------------------");
12             Publisher pub2 = new Publisher();
13             Subscriber user2 = new Subscriber("中国联通", pub2);
14             pub2.Call("号码10010");
15             Console.ReadKey();
16         }
17     }

结果如下:

1.EventHandler<T>在.NET Framework 2.0中引入,义了一个处理程序,它返回void,接受两个参数。

1 public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);

第一个参数(sender)是一个对象,包含事件的发送者。
第二个参数(e)提供了事件的相关信息,参数随不同的事件类型而改变(继承EventArgs)。
.NET1.0为所有不同数据类型的事件定义了几百个委托,有了泛型委托EventHandler<T>后,不再需要委托了。

2.EventArgs,标识表示包含事件数据的类的基类,并提供用于不包含事件数据的事件的值。

1 [System.Runtime.InteropServices.ComVisible(true)]
2 public class EventArgs

3.同时可以根据编程方式订阅事件

1     Publisher pub = new Publisher();
2     pub.Publication += Close;
3     ...
4     //添加一个方法
5     static void Close(object sender, CustomEventArgs a)
6     {
7             // 关闭电话
8     }

4.Consumer类为事件监听器当触发事件时可获取当前发布者对应自定义信息对象,可以根据需要做逻辑编码,再执行事件所订阅的相关处理。增加事件订阅/发布机制的健壮性。

5.以线程安全的方式触发事件    

1 EventHandler<CustomEventArgs> publication = Publication;

触发事件是只包含一行代码的程序。这是C#6.0的功能。在之前版本,触发事件之前要做为空判断。同时在进行null检测和触发之间,可能另一个线程把事件设置为null。所以需要一个局部变量。在C#6.0中,所有触发都可以使用null传播运算符和一个代码行取代。

1 Publication?.Invoke(this, e);

注意尽管定义的类中的事件可基于任何有效委托类型,甚至是返回值的委托,但一般还是建议使用 EventHandler 使事件基于 .NET Framework 模式。

 

线程安全方式触发事件

在上面的例子中,过去常见的触发事件有三种方式:

 1             //版本1
 2             if (Publication != null)
 3             {
 4                 Publication();//触发事件
 5             }
 6 
 7             //版本2
 8             var temp = Publication;
 9             if (temp != null)
10             {
11                 temp();//触发事件
12             }
13 
14             //版本3
15             var temp = Volatile.Read(ref Publication);
16             if (temp != null)
17             {
18                 temp();//触发事件
19             }

版本1会发生NullReferenceException异常。

版本2的解决思路是,将引用赋值到临时变量temp中,后者引用赋值发生时的委托链。所以temp复制后即使另一个线程更改了AfterPublication对象也没有关系。委托是不可变得,所以理论上行得通。但是编译器可能通过完全移除变量temp的方式对上述代码进行优化所以仍可能抛出NullReferenceException.

版本3Volatile.Read()的调用,强迫Publication在这个调用发生时读取,引用真的必须赋值到temp中,编译器优化代码。然后temp只有再部位null时才被调用。

版本3最完美技术正确,版本2也是可以使用的,因为JIT编译机制上知道不该优化掉变量temp,所以在局部变量中缓存一个引用,可确保堆应用只被访问一次。但将来是否改变不好说,所以建议采用版本3。

 

 

事件揭秘

我们重新审视基础事件里的一段代码:

1     public delegate void NoReturnWithParameters();
2     static event NoReturnWithParameters NoReturnWithParametersEvent;

通过反编译我们可以看到:

编译器相当于做了一次如下封装:

 1 NoReturnWithParameters parameters;
 2 private event NoReturnWithParameters NoReturnWithParametersEvent
 3 {
 4      add {  NoReturnWithParametersEvent+=parameters; }
 5      remove {  NoReturnWithParametersEvent-=parameters; }
 6 }
 7 /*
 8  * 作者:Jonins
 9  * 出处:http://www.cnblogs.com/jonins/
10  */

声明了一个私有的委托变量,开放两个方法add和remove作为事件访问器用于(+=、-=),NoReturnWithParametersEvent被编译为Private从而实现封装外部无法触发事件。

1.委托类型字段是对委托列表头部的引用,事件发生时会通知这个列表中的委托。字段初始化为null,表明无侦听者等级对该事件的关注。

2.即使原始代码将事件定义为Public,委托字段也始终是Private.目的是防止外部的代码不正确的操作它。

3.方法add_xxxremove_xxxC#编译器还自动为方法生成代码调用(System.Delegate的静态方法CombineRemove)。

4.试图删除从未添加过的方法,Delegate的Remove方法内部不做任何事经,不会抛出异常或任何警告,事件的方法集体保持不变。

5.addremove方法以线程安全的一种模式更新值(Interlocked Anything模式)。

 

结语

类或对象可以通过事件向其他类或对象通知发生的相关事情。事件使用的是发布/订阅机制,声明事件的类为发布类,而对这个事件进行处理的类则为订阅类。而订阅类如何知道这个事件发生并处理,这时候需要用到委托。事件的使用离不开委托。但是事件并不是委托的一种(事件是特殊的委托的说法并不正确),委托属于类型(type)它指的是集合(类,接口,结构,枚举,委托),事件是定义在类里的一个成员。

 

参考文献

 

CLR via C#(第4版) Jeffrey Richter

C#高级编程(第7版) Christian Nagel  (版9、10对事件部分没有多大差异)

果壳中的C# C#5.0权威指南 Joseph Albahari

https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/events/index

...


 

转载于:https://www.cnblogs.com/jonins/p/9324771.html

修改:File "C:\Users\Administrator\Desktop\project\core\event_center.py", line 114 def subscribe(self, event_type, callback): """订阅事件""" if event_type not in self.subscribers: self.subscribers[event_type] = [] 已重新声明上文定义的无用法的 'subscribe'未使用局部函数 '__init__',从外部作用域隐藏名称 'self'函数中的变量应小写,从外部作用域隐藏名称 'event'代码#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 优化后的事件中心模块 (最终稳定版) """ import logging import threading import uuid import time from enum import Enum, auto from typing import Tuple, Dict, List, Callable, Optional, Any, Set, DefaultDict, Union from collections import defaultdict from dataclasses import dataclass, field from concurrent.futures import ThreadPoolExecutor # 初始化日志 logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) class EventType(Enum): """完整事件类型枚举""" # 基础事件类型 MODULE_RUN = auto() ANALYSIS_RESULT = auto() MODULE_ERROR = auto() REGISTER_UI = auto() GET_RESULTS = auto() # 系统事件 SYSTEM_STARTUP = auto() SYSTEM_SHUTDOWN = auto() MODULE_READY = auto() ERROR = auto() # 模块特定事件 INPUT_ANALYSIS_START = auto() INPUT_ANALYSIS_END = auto() COMBINATION_ANALYSIS_START = auto() COMBINATION_ANALYSIS_END = auto() FOLLOW_ANALYSIS_START = auto() FOLLOW_ANALYSIS_UPDATE = auto() TREND_ANALYSIS_REQUEST = auto() TREND_ANALYSIS_REPORT = auto() NUMBER_GENERATION_START = auto() NUMBER_GENERATION_FINISH = auto() # 测试事件 TEST_EVENT = auto() @dataclass class Event: """事件数据类""" type: Union[str, EventType] # 必须放在非默认参数位置 source: str # 必须放在非默认参数位置 target: Optional[str] = None event_id: str = field(default_factory=lambda: str(uuid.uuid4())) token: Optional[str] = None data: Optional[Dict[str, Any]] = field(default_factory=dict) timestamp: float = field(default_factory=time.time) def __post_init__(self): """数据验证和类型转换""" if isinstance(self.type, EventType): self.type = self.type.name if not isinstance(self.type, str) or not self.type: raise ValueError("type 必须是非空字符串或EventType枚举") if not isinstance(self.source, str) or not self.source: raise ValueError("source 必须是非空字符串") class EventCenter: """线程安全的事件中心(最终优化版)""" _instance = None _lock = threading.Lock() _executor = ThreadPoolExecutor(max_workers=10, thread_name_prefix="EventWorker") def __new__(cls): with cls._lock: if cls._instance is None: cls._instance = super().__new__(cls) cls._instance.__initialized = False return cls._instance def __init__(self): """初始化事件中心(单例模式)""" def __init__(self): self.subscribers = {} if getattr(self, '__initialized', False): return self.__initialized = True self._subscribers: DefaultDict[str, List[Tuple[Callable[[Event], None], Optional[str]]]] = defaultdict(list) self._event_history: Dict[str, Event] = {} self._pending_acks: Set[str] = set() self._ack_timeout = 5.0 self._stats = { 'total_events': 0, 'failed_events': 0, 'delivered_events': 0 } def subscribe(self, event_type: Union[str, EventType], handler: Callable[[Event], None], token: Optional[str] = None) -> None: """订阅事件(支持token过滤)""" event_type_str = event_type.name if isinstance(event_type, EventType) else str(event_type) with self._lock: self._subscribers[event_type_str].append((handler, token)) logger.debug(f"已订阅事件: {event_type_str}, token: {token}") def subscribe(self, event_type, callback): """订阅事件""" if event_type not in self.subscribers: self.subscribers[event_type] = [] self.subscribers[event_type].append(callback) def publish(self, event_type, data): """发布事件""" if event_type in self.subscribers: for callback in self.subscribers[event_type]: callback(data) def unsubscribe(self, event_type: Union[str, EventType], handler: Callable[[Event], None]) -> bool: """取消订阅事件""" event_type_str = event_type.name if isinstance(event_type, EventType) else str(event_type) with self._lock: if event_type_str not in self._subscribers: return False before = len(self._subscribers[event_type_str]) self._subscribers[event_type_str] = [ (h, t) for h, t in self._subscribers[event_type_str] if h != handler ] removed = before != len(self._subscribers[event_type_str]) if removed: logger.debug(f"已取消订阅: {event_type_str}") return removed def publish(self, event, require_ack=False, async_handle=True) -> bool: """发布事件(支持同步/异步处理)""" # 增强事件验证 try: # 确保事件有必要的属性 if not hasattr(event, 'type') or not hasattr(event, 'source'): raise AttributeError("事件缺少必要属性: type或source") # 自动生成事件ID(如果缺失) if not hasattr(event, 'event_id') or not event.event_id: event.event_id = str(uuid.uuid4()) # 执行事件验证 if hasattr(event, '__post_init__'): event.__post_init__() else: # 手动验证基本属性 if not isinstance(event.type, (str, EventType)): raise TypeError("type必须是字符串或EventType") if not isinstance(event.source, str) or not event.source: raise ValueError("source必须是非空字符串") except (ValueError, TypeError, AttributeError) as e: # 处理预期的验证错误 logger.error(f"事件验证失败: {e}") with self._lock: self._stats['failed_events'] += 1 return False except Exception as e: # 记录所有其他异常 logger.exception(f"事件验证过程中发生意外错误: {e}") with self._lock: self._stats['failed_events'] += 1 return False # 主事件处理逻辑 try: with self._lock: if event.event_id in self._event_history: logger.warning(f"重复事件ID: {event.event_id}") return False self._event_history[event.event_id] = event self._stats['total_events'] += 1 if require_ack: self._pending_acks.add(event.event_id) handlers = self._get_matching_handlers(event) if not handlers: logger.debug(f"没有处理器订阅: {event.type}") return True if async_handle: self._executor.submit(self._dispatch_event, event, handlers, require_ack) else: self._dispatch_event(event, handlers, require_ack) return True except (KeyError, AttributeError) as e: # 处理内部数据结构错误 logger.error(f"内部数据结构错误: {e}") with self._lock: self._stats['failed_events'] += 1 return False except Exception as e: # 捕获所有其他异常 logger.exception(f"发布事件时发生未预期错误: {e}") with self._lock: self._stats['failed_events'] += 1 return False def _get_matching_handlers(self, event: Event) -> List[Callable[[Event], None]]: """获取匹配的事件处理器""" with self._lock: return [ h for h, t in self._subscribers.get(event.type, []) if t is None or t == event.token ] def _dispatch_event(self, event: Event, handlers: List[Callable[[Event], None]], require_ack: bool) -> None: """分发事件到处理器""" for handler in handlers: try: handler(event) with self._lock: self._stats['delivered_events'] += 1 logger.debug(f"事件处理成功: {event.event_id[:8]}") except Exception as e: logger.exception(f"处理器异常: {e}") if require_ack and event.target: self._wait_for_ack(event) def _wait_for_ack(self, event: Event) -> None: """等待事件确认""" start = time.time() while time.time() - start < self._ack_timeout: with self._lock: if event.event_id not in self._pending_acks: return time.sleep(0.05) logger.warning(f"事件确认超时: {event.event_id[:8]}") def get_stats(self) -> Dict[str, int]: """获取事件中心统计信息""" with self._lock: return self._stats.copy() def clear(self): """重置事件中心状态(测试专用)""" with self._lock: self._subscribers.clear() self._event_history.clear() self._pending_acks.clear() self._stats = { 'total_events': 0, 'failed_events': 0, 'delivered_events': 0 } # 全局单例实例 event_center = EventCenter() # ======================== 完整测试套件 ======================== import pytest from dataclasses import make_dataclass @pytest.fixture def event_center_instance(): """创建独立的EventCenter实例用于测试""" center = EventCenter() center.clear() return center def test_event_subscription(event_center_instance): """测试事件订阅机制""" handled_events = [] def handler(event: Event): handled_events.append(event) # 订阅并发布测试事件 event_center_instance.subscribe(EventType.TEST_EVENT, handler) test_event = Event( type=EventType.TEST_EVENT, source="pytest", data={"test": "subscription"} ) assert event_center_instance.publish(test_event) is True time.sleep(0.1) # 等待异步处理完成 assert len(handled_events) == 1 assert handled_events[0].data["test"] == "subscription" stats = event_center_instance.get_stats() assert stats["total_events"] == 1 assert stats["delivered_events"] == 1 assert stats["failed_events"] == 0 def test_event_validation_failure(event_center_instance): """测试无效事件处理""" # 用例1:完全无效的事件对象 class InvalidEvent: pass # 用例2:缺少必要属性的事件 PartialEvent = make_dataclass('PartialEvent', [('type', str)], namespace={'source': None}) partial_event = PartialEvent(type="TEST") # 用例3:属性类型错误的事件 @dataclass class WrongTypeEvent: type: int = 123 # 应该是字符串或EventType source: str = "test" # 用例4:空source属性的事件 @dataclass class EmptySourceEvent: type: str = "TEST" source: str = "" # 空字符串 for invalid_event in [InvalidEvent(), partial_event, WrongTypeEvent(), EmptySourceEvent()]: assert event_center_instance.publish(invalid_event) is False # 验证统计计数 stats = event_center_instance.get_stats() assert stats["total_events"] == 0 assert stats["failed_events"] == 4 assert stats["delivered_events"] == 0 def test_duplicate_event_id(event_center_instance): """测试重复事件ID检测""" event = Event( type=EventType.TEST_EVENT, source="pytest", event_id="fixed-id-123" ) # 首次发布应成功 assert event_center_instance.publish(event) is True # 重复发布应失败 assert event_center_instance.publish(event) is False stats = event_center_instance.get_stats() assert stats["total_events"] == 1 assert stats["failed_events"] == 0 # 重复事件不算验证失败 assert stats["delivered_events"] == 0 # 没有订阅者 def test_token_filtering(event_center_instance): """测试基于token的事件过滤""" handled_events = [] def handler(event: Event): handled_events.append(event) # 订阅特定token的事件 event_center_instance.subscribe(EventType.TEST_EVENT, handler, token="secret") # 发布不带token的事件(不应被处理) event1 = Event( type=EventType.TEST_EVENT, source="pytest", data={"test": "no token"} ) # 发布带错误token的事件(不应被处理) event2 = Event( type=EventType.TEST_EVENT, source="pytest", token="wrong", data={"test": "wrong token"} ) # 发布正确token的事件(应被处理) event3 = Event( type=EventType.TEST_EVENT, source="pytest", token="secret", data={"test": "correct token"} ) assert event_center_instance.publish(event1) is True assert event_center_instance.publish(event2) is True assert event_center_instance.publish(event3) is True time.sleep(0.1) # 等待异步处理完成 assert len(handled_events) == 1 assert handled_events[0].data["test"] == "correct token" stats = event_center_instance.get_stats() assert stats["total_events"] == 3 assert stats["delivered_events"] == 1 assert stats["failed_events"] == 0 def test_async_handling(event_center_instance): """测试异步事件处理""" handled_events = [] def slow_handler(event: Event): time.sleep(0.2) handled_events.append(event) event_center_instance.subscribe(EventType.TEST_EVENT, slow_handler) # 发布两个事件 event1 = Event(type=EventType.TEST_EVENT, source="pytest", data={"id": 1}) event2 = Event(type=EventType.TEST_EVENT, source="pytest", data={"id": 2}) # 异步发布 assert event_center_instance.publish(event1) is True assert event_center_instance.publish(event2) is True # 立即检查(应尚未处理) assert len(handled_events) == 0 # 等待足够时间 time.sleep(0.3) assert len(handled_events) == 2 assert {e.data['id'] for e in handled_events} == {1, 2} def test_ack_mechanism(event_center_instance): """测试事件确认机制""" ack_received = False def ack_handler(event: Event): nonlocal ack_received # 模拟目标模块发送ACK if event.type == "ACK": ack_received = True # 从待确认集合中移除 with event_center_instance._lock: if event.data.get("ack_for") in event_center_instance._pending_acks: event_center_instance._pending_acks.remove(event.data["ack_for"]) # 订阅ACK事件(模拟) event_center_instance.subscribe("ACK", ack_handler) # 发布需要ACK的事件 event = Event( type=EventType.TEST_EVENT, source="pytest", target="test_target", data={"require_ack": True} ) # 发布测试事件(要求ACK) assert event_center_instance.publish(event, require_ack=True) is True # 检查事件是否在待确认集合中 with event_center_instance._lock: assert event.event_id in event_center_instance._pending_acks # 发布ACK事件(模拟目标模块的响应) ack_event = Event( type="ACK", source="test_target", data={"ack_for": event.event_id} ) event_center_instance.publish(ack_event) # 等待ACK处理 time.sleep(0.1) assert ack_received is True # 检查pending_acks应被移除 with event_center_instance._lock: assert event.event_id not in event_center_instance._pending_acks def test_event_auto_id_generation(event_center_instance): """测试自动生成事件ID""" # 创建没有event_id的事件 event = Event( type=EventType.TEST_EVENT, source="pytest", data={"auto_id": True} ) del event.event_id # 移除自动生成的ID assert event_center_instance.publish(event) is True assert hasattr(event, 'event_id') and event.event_id assert len(event.event_id) == 36 # UUID长度验证 def test_concurrent_publishing(event_center_instance): """测试并发事件发布""" from concurrent.futures import ThreadPoolExecutor handled_events = [] event_count = 50 def handler(event: Event): handled_events.append(event) event_center_instance.subscribe(EventType.TEST_EVENT, handler) def publish_event(i): event = Event( type=EventType.TEST_EVENT, source=f"thread-{i}", data={"index": i} ) event_center_instance.publish(event) # 使用线程池并发发布事件 with ThreadPoolExecutor(max_workers=10) as executor: executor.map(publish_event, range(event_count)) # 等待所有事件处理完成 time.sleep(0.5) assert len(handled_events) == event_count assert len({e.data['index'] for e in handled_events}) == event_count
最新发布
08-21
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值