如何在C#的Parallel.ForEach中追踪ETW事件
用ETW追踪Parallel.ForEach内部Task的启动与完成
一、TPL相关ETW事件基础
Parallel.ForEach底层依赖Task Parallel Library(TPL)实现并行调度,每个迭代逻辑由TPL的Task执行,对应以下核心ETW事件,触发时机明确:
- TaskStart(事件ID 100):Task开始执行迭代逻辑前触发
- TaskCompleted(事件ID 101):Task执行完迭代逻辑(包括finally块)后触发
- TaskScheduled(事件ID 102):Task被调度到线程池等待执行时触发
TPL的ETW提供者名称为System.Threading.Tasks.TplEventSource,GUID为{2e5dba47-a3d2-4d16-8ee0-6671ffdcd7b5}。
二、捕获ETW事件的两种方式
1. 使用PerfView可视化捕获
这是最常用的ETW事件分析方式,无需修改代码:
- 打开PerfView,点击
Collect->Collect - 在
Additional Providers输入框中添加System.Threading.Tasks.TplEventSource,确认后启动收集 - 运行你的Parallel.ForEach代码,执行完毕后停止收集
- 在PerfView的分析界面,筛选
Tasks相关事件,即可查看每个迭代Task的启动、完成时间线
2. 代码中自定义监听事件
如果需要在代码中直接处理ETW事件,可以通过EventListener订阅TPL的事件源,示例如下:
using System; using System.Collections.Generic; using System.Linq; using System.Diagnostics.Tracing; using System.Threading; public class TplTaskEventListener : EventListener { protected override void OnEventSourceCreated(EventSource eventSource) { // 订阅TPL的事件源 if (eventSource.Name.Equals("System.Threading.Tasks.TplEventSource", StringComparison.Ordinal)) { EnableEvents(eventSource, EventLevel.Informational, EventKeywords.All); } } protected override void OnEventWritten(EventWrittenEventArgs eventData) { switch (eventData.EventId) { case 100: // TaskStart事件 Console.WriteLine($"[ETW] Task启动: TaskId={eventData.Payload[0]}, 操作名称={eventData.Payload[1]}"); break; case 101: // TaskCompleted事件 Console.WriteLine($"[ETW] Task完成: TaskId={eventData.Payload[0]}, 操作名称={eventData.Payload[1]}, 耗时={eventData.Payload[2]}ms"); break; } } } class Program { static void Main() { // 启动自定义ETW监听器 var tplListener = new TplTaskEventListener(); List<int> integerList = Enumerable.Range(0, 2).ToList(); Parallel.ForEach(integerList, i => { try { Console.WriteLine($"Print of i = {i}, thread = {Thread.CurrentThread.ManagedThreadId}, thread status = {Thread.CurrentThread.ThreadState}"); } catch (Exception ex) { Console.WriteLine($"Catch value of Exception = {ex.ToString()}, Thread Id {Thread.CurrentThread.ManagedThreadId}, Thread State {Thread.CurrentThread.ThreadState}"); } finally { Console.WriteLine($"Finally block value of i = {i}, thread = {Thread.CurrentThread.ManagedThreadId}, thread status = {Thread.CurrentThread.ThreadState}"); } }); Console.ReadLine(); } }
三、事件触发时机与代码逻辑的对应关系
针对你的Parallel.ForEach代码,事件触发顺序为:
TaskScheduled事件:迭代Task被线程池调度TaskStart事件:触发后进入迭代的try块,执行Console.WriteLine- 执行
finally块的Console.WriteLine TaskCompleted事件:finally块执行完毕后触发
内容的提问来源于stack exchange,提问作者Sks
相关产品推荐
相关产品推荐

