You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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代码,事件触发顺序为:

  1. TaskScheduled事件:迭代Task被线程池调度
  2. TaskStart事件:触发后进入迭代的try块,执行Console.WriteLine
  3. 执行finally块的Console.WriteLine
  4. TaskCompleted事件:finally块执行完毕后触发

内容的提问来源于stack exchange,提问作者Sks

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 16:28:17