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

EventSource 是什么,为什么不能直接 new EventListener
EventSource 是 .NET 提供的轻量级、高性能事件发布机制,专为生产环境诊断设计;它本身不负责监听,只负责「写」。而 EventListener 是抽象基类,必须继承并重写 OnEventSourceCreated 和 OnEventWritten 才能接收事件——你不能直接 new EventListener(),否则毫无作用。
如何让自定义 EventListener 捕获并发相关事件(如 TaskScheduler、ThreadPool)
.NET 运行时自带多个内部 EventSource,比如 System.Threading.Tasks.TplEventSource、System.Runtime.ThreadPoolEventSource,它们默认是禁用的。要监听,必须在 OnEventSourceCreated 中显式启用对应事件源,并指定关键词(keyword)和等级(level)。
- 关键词决定捕获哪类事件:例如
TaskScheduler事件常用EventSource.Settings.TaskScheduler(实际值为 0x10),ThreadPool用EventSource.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.RunContinuationsAsynchronously或Task.Run创建的任务才触发ScheduledTask;同步 continuation(如ContinueWith默认行为)不会发事件 -
ThreadPoolEventSource的ThreadRequested只在“线程不足需扩容”时发出,空闲线程复用过程无事件 - 若程序启动后才创建
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 都有自己隐含的触发边界和采样逻辑,想看到某类行为,得先确认它确实属于该事件源的输出范畴。










