Environment.CurrentManagedThreadIdusing System;
void Foo()
{
int threadId = Environment.CurrentManagedThreadId;
Console.WriteLine($"当前线程ID: {threadId}");
}
👉 99% 场景用这个就够了
async / await(重点)async Task FooAsync()
{
int threadId = Thread.CurrentThread.ManagedThreadId;
await Task.Delay(1000);
int threadId2 = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine($"{threadId} -> {threadId2}");
}
await 前后线程可能不一样ManagedThreadId 会变化👉 如果你想标识的是**“一次逻辑执行流程”**,而不是线程本身,看下面👇
AsyncLocal<T>static AsyncLocal<Guid> _traceId = new();
void Init()
{
_traceId.Value = Guid.NewGuid();
}
void Foo()
{
Console.WriteLine($"逻辑ID: {_traceId.Value}");
}
✔ 在 async/await 链路中保持一致
✔ 跨线程
✔ 非常适合:
using System.Runtime.InteropServices;
[DllImport("kernel32.dll")]
static extern uint GetCurrentThreadId();
uint osThreadId = GetCurrentThreadId();
| 场景 | 推荐方式 |
|---|---|
| 普通多线程 | Environment.CurrentManagedThreadId |
| async / await | AsyncLocal<T> |
| 日志追踪 | AsyncLocal<Guid> |
| 底层/调试 | GetCurrentThreadId() |
“逻辑级唯一标识”本质上就是:不关心你在哪个线程上跑,只关心“这是同一次业务/任务/调用链”。
线程 ID = 人在哪张椅子上坐着 逻辑 ID = 人是谁
async/await 会“换椅子”,但你还是你。
逻辑级唯一标识就是用来标记“你是谁”。
async Task DoWorkAsync()
{
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
await Task.Delay(500);
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
}
可能输出:
7
12
👉 同一次 DoWorkAsync 调用,却跑在不同线程上
如果你:
线程 ID 会直接把你坑死
| 场景 | 为什么要逻辑 ID |
|---|---|
| 日志 | 把一次请求的所有日志串起来 |
| 后台任务 | 区分多个并发任务 |
| WinUI / WPF | UI → 后台 → 回 UI |
| Web API | 每个请求一个 TraceId |
| 工具程序 | 一个“操作”跨多个方法 |
AsyncLocal<T>AsyncLocal<T> 是 为 async 而生的 ThreadLocal 升级版。
static class TraceContext
{
public static AsyncLocal<Guid> TraceId = new();
}
async Task HandleAsync()
{
TraceContext.TraceId.Value = Guid.NewGuid();
await Step1();
await Step2();
}
async Task Step1()
{
await Task.Delay(100);
Log("Step1");
}
void Log(string msg)
{
Console.WriteLine($"[{TraceContext.TraceId.Value}] {msg}");
}
[7e2c7c9b-0a7d-4df0-9b89-8b0c88d4a2aa] Step1
👉 线程可能变,TraceId 永远不变
ThreadLocal<T>?| 特性 | ThreadLocal | AsyncLocal |
|---|---|---|
| 跨 await | ❌ | ✅ |
| 跨线程池 | ❌ | ✅ |
| async 支持 | ❌ | ✅ |
| 现代 .NET | 不推荐 | 推荐 |
async void Button_Click(...)
{
TraceContext.TraceId.Value = Guid.NewGuid();
await Task.Run(DoHeavyWork);
UpdateUI();
}
void DoHeavyWork()
{
Log("后台计算");
}
void UpdateUI()
{
Log("回到 UI");
}
日志里三步 TraceId 完全一致 哪怕后台和 UI 是不同线程。
TraceContext.TraceId.Value // null / Guid.Empty
✅ 解决:只在入口初始化
Parallel.ForEach(list, item =>
{
// 共享同一个 TraceId(错)
});
✅ 正确做法:
Parallel.ForEach(list, item =>
{
TraceContext.TraceId.Value = Guid.NewGuid();
});
线程 ID 用来“调试线程问题” 逻辑 ID 用来“理解业务发生了什么”