c# 如何用 EventSource 和 EventListener 监控内部并发事件

10次阅读

EventSource 是 .net 轻量级事件发布机制,只负责“写”;EventListener 是抽象基类,不可直接实例化,必须继承并重写 OnEventSourceCreated 和 OnEventWritten 才能接收事件。

c# 如何用 EventSource 和 EventListener 监控内部并发事件

EventSource 是什么,为什么不能直接 new EventListener

EventSource 是 .NET 提供的轻量级、高性能事件发布机制,专为生产环境诊断设计;它本身不负责监听,只负责「写」。而 EventListener 是抽象基类,必须继承并重写 OnEventSourceCreatedOnEventWritten 才能接收事件——你不能直接 new EventListener(),否则毫无作用。

如何让自定义 EventListener 捕获并发相关事件(如 TaskScheduler、ThreadPool)

.NET 运行时自带多个内部 EventSource,比如 System.Threading.Tasks.TplEventSourceSystem.Runtime.ThreadPoolEventSource,它们默认是禁用的。要监听,必须在 OnEventSourceCreated 中显式启用对应事件源,并指定关键词(keyword)和等级(level)。

  • 关键词决定捕获哪类事件:例如 TaskScheduler 事件常用 EventSource.Settings.TaskScheduler(实际值为 0x10),ThreadPoolEventSource.Settings.ThreadPool(0x20)
  • 等级至少设为 EventLevel.Verbose 才能看到调度细节(如任务入队、线程唤醒)
  • 必须调用 EnableEvents(eventSource, level, keywords),且该调用需在 OnEventSourceCreated 内完成,延迟启用会丢事件
public class ConcurrencyEventListener : EventListener {     protected override void OnEventSourceCreated(EventSource eventSource)     {         if (eventSource.Name == "System.Threading.Tasks.TplEventSource")         {             EnableEvents(eventSource, EventLevel.Verbose, (EventKeywords)0x10); // TaskScheduler         }         else if (eventSource.Name == "System.Runtime.ThreadPoolEventSource")         {             EnableEvents(eventSource, EventLevel.Verbose, (EventKeywords)0x20); // ThreadPool         }     }      protected override void OnEventWritten(EventWrittenEventArgs eventData)     {         if (eventData.EventName is "ScheduledTask" or "ThreadRequested" or "WorkerThreadStart")         {             Console.WriteLine($"[{eventData.EventSource.Name}] {eventData.EventName}: {string.Join(", ", eventData.PayloadNames.Zip(eventData.Payload, (n, v) => $"{n}={v}"))}");         }     } }

常见漏掉的并发事件和调试陷阱

很多人以为启用了 TplEventSource 就能看见所有 Task 行为,但实际有三个关键限制:

  • TaskCreationOptions.RunContinuationsAsynchronouslyTask.Run 创建的任务才触发 ScheduledTask;同步 continuation(如 ContinueWith 默认行为)不会发事件
  • ThreadPoolEventSourceThreadRequested 只在“线程不足需扩容”时发出,空闲线程复用过程无事件
  • 若程序启动后才创建 ConcurrencyEventListener 实例,此前已发生的调度事件(尤其是 appDomain 初始化阶段的)完全丢失——必须尽早 new 并保持存活
  • 事件负载高时(如每秒数千任务),OnEventWritten 是同步调用,阻塞会导致事件被丢弃;生产环境应异步缓冲或限流处理

验证是否真收到了并发事件的最简方法

不要依赖日志滚动——写个可复现的最小触发片段,配合断点或计数器确认:

var listener = new ConcurrencyEventListener(); // 确保 listener 实例不被 GC(比如存为 static 字段)  Task.Run(() => Thread.Sleep(1)); // 触发 ThreadPool + Tpl 调度事件 Task.Factory.StartNew(() => { }, TaskCreationOptions.PreferFairness);  // 等待一小会儿再 Dispose,避免事件还在管道中 await Task.Delay(100); listener.Dispose();

如果 OnEventWritten 完全没被调用,优先检查 EventSource.Name 是否拼错(大小写敏感)、.NET 版本是否支持(TplEventSource 在 .NET Core 2.1+ 稳定,.NET Framework 4.6.2+ 有但字段名不同)。

并发监控不是开箱即用的开关,每个 EventSource 都有自己隐含的触发边界和采样逻辑,想看到某类行为,得先确认它确实属于该事件源的输出范畴。

text=ZqhQzanResources