从线程中获取线程ID

368

在C#中,例如在调试线程时,可以看到每个线程的ID。

我找不到一种程序化地获取相同线程的方法。我甚至无法获取当前线程的ID(在Thread.currentThread的属性中)。

那么,我想知道Visual Studio如何获取线程的ID,并且是否有一种方法可以获取ID为2345的线程的句柄?

12个回答

492

GetThreadId 函数返回给定本地线程的 ID。我确信有办法使其适用于托管线程,你只需要找到线程句柄并将其传递给该函数即可。

对于托管线程,请使用System.Environment.CurrentManagedThreadId

以下是旧 SDK 选项,但首选使用 System.Environment.CurrentManagedThreadId.

  • GetCurrentThreadId 函数返回当前线程的 ID。自 .NET 2.0 起已弃用 GetCurrentThreadId
  • Thread.CurrentThread.ManagedThreadId 返回当前托管线程的唯一标识符,与 System.Environment.CurrentManagedThreadId 相同,然而,System.Environment.CurrentManagedThreadId 更受推荐 (CA1840)。

98
自从我找到并打出这段代码后被告知它已经过时,现在应该使用 Thread.CurrentThread.ManagedThreadId 来实现相同的功能。 - James
4
将ManagedThreadId视为识别线程的鲁棒方法并不可靠,因为应用程序会重复使用ManagedThreadId属性标识符。因此,在某些情况下,它不能可靠地用作线程标识符,可能会在代码行...处出现异常:"已经添加了具有相同键的项"。创建线程时,请给它一个唯一的名称。 - Forer
22
这篇文章中流传着一些非常糟糕的建议。有几个人建议使用"ManagedThreadId"来标识线程。我编辑了这篇文章以删除这个建议。很少有人指出不同类型的线程ID存在差异。托管线程ID并不等同于不受管辖的线程ID,如果人们复制和粘贴该代码,可能会发生一些非常微妙的同步错误。MSDN上Thread类的文档对此非常清楚,请查看类级别的备注。 - ShadowChaser
5
不过,你不会根据ID进行同步,而是使用像互斥锁这样的同步原语。这仅用于调试目的。 - Blindy
12
我想发布这条评论以提醒大家,当在SetWindowsHookEx中使用时,System.Threading.Thread.CurrentThread.ManagedThreadId至少不起作用。相反,我们必须从本机win32函数GetCurrentThreadId()中获取线程ID。 - King King
显示剩余3条评论

96

在C#中,例如调试线程时,可以看到每个线程的ID。

这些将是托管线程的ID。 ManagedThreadIdThread 的成员,因此您可以从任何 Thread 对象获取ID。 这将为您获取当前的ManagedThreadID

Thread.CurrentThread.ManagedThreadId

要通过其操作系统线程ID(而不是ManagedThreadID)获取操作系统线程,您可以尝试使用一些linq。

int unmanagedId = 2345;
ProcessThread myThread = (from ProcessThread entry in Process.GetCurrentProcess().Threads
   where entry.Id == unmanagedId 
   select entry).First();

看起来没有办法枚举托管线程,而且ProcessThread与Thread之间也没有关系,所以通过其ID获取托管线程是一项艰巨的任务。

有关托管和非托管线程的更多详细信息,请参阅此MSDN文章


4
为什么没有其他人想出这个简单的答案? - Stefan Steinegger
3
这行代码无法运行。GetCurrentProcess().Threads返回一个ProcessThreadCollection,而不是Threads,无法转换。我看不到容易的解决办法。 - mafu
2
@ mafutrct,已更新答案。那个属性真应该叫做.ProcessThreads!谢谢。 - badbod99
2
建议重新撰写此帖以使其更清晰,表明这两个线程 ID 是不同的。如果有人未能阅读最后一句话,他们将只是插入 ManagedThreadId 并尝试将其映射到 ProcessThread.Id,从而造成混乱。 - ShadowChaser
1
我已经添加了一个链接到一个有用的MSDN文章,突出了它们之间的区别。然而,这个问题与获取线程ID以进行调试有关(在这种情况下是ManagedThreadID)。我认为在回答中加入操作系统和托管线程之间差异的细节是没有用的。 - badbod99

50

您可以使用已弃用的AppDomain.GetCurrentThreadId来获取当前正在运行的线程的ID。此方法使用PInvoke调用Win32 API方法GetCurrentThreadID,并将返回Windows线程ID。

该方法被标记为已弃用,因为.NET线程对象不对应于单个Windows线程,因此没有稳定的ID可以由Windows针对给定的.NET线程返回。

有关此情况的更多原因,请参见configurator的答案。


2
注意:在 .Net Core 2.2 中,需要注意 AppDomain.GetCurrentThreadId(我通过 MethodInfo 调用时已过时)返回的是托管线程 ID(无法匹配 Process.GetCurrentProcess().Threads 集合)。 - brewmanz

36

获取操作系统 ID 的方法如下:

AppDomain.GetCurrentThreadId()

1
GetHashCode并不一定是唯一的!不应该使用它来标识线程。 - Dror Helper
2
如果你想获取操作系统线程ID,可以使用AppDomain.GetCurrentThreadId()方法,但是多个.NET线程理论上可以共享同一个操作系统线程。Thread.GetHashCode()方法保证返回一个在整个进程中唯一的值,这可能是你想要的。 - Mark Byers
3
该方法已被标记为“不推荐使用”,原因充分。请参阅我的回答和配置器的回答以获取更全面的信息。 - Paul Turner
4
这是获取操作系统线程ID的唯一方法。并且应将其标记为正确答案。 尽管我不会再依赖它。 - LolaRun
1
AppDomain.GetCurrentThreadId() is obsolete: AppDomain.GetCurrentThreadId has been deprecated because it does not provide a stable Id when managed threads are running on fibers (aka lightweight threads). To get a stable identifier for a managed thread, use the ManagedThreadId property on Thread.Usage: Thread.CurrentThread.ManagedThreadId - L J
显示剩余5条评论

23
根据MSDN的说法:

操作系统的ThreadId与托管线程之间没有固定关系,因为非托管主机可以控制托管和非托管线程之间的关系。具体而言,高级主机可以使用CLR Hosting API来调度多个托管线程与同一个操作系统线程,或者将托管线程移动到不同的操作系统线程中。

因此,基本上Thread对象并不一定对应于操作系统线程——这就是为什么它没有公开本机ID的原因。

在VS2010中的调试/线程窗口显示“托管线程ID”。我该如何获取它? - Pavel Radzivilovsky
1
使用ManagedThreadID属性http://msdn.microsoft.com/en-us/library/system.threading.thread.managedthreadid.aspx。但这与操作系统线程ID不同。 - configurator

15

对于那些即将进行黑客攻击的人:

    public static int GetNativeThreadId(Thread thread)
    {
        var f = typeof(Thread).GetField("DONT_USE_InternalThread",
            BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance);

        var pInternalThread = (IntPtr)f.GetValue(thread);
        var nativeId = Marshal.ReadInt32(pInternalThread, (IntPtr.Size == 8) ? 548 : 348); // found by analyzing the memory
        return nativeId;
    }

14
要获取当前线程的ID,请使用 `Thread.CurrentThread.ManagedThreadId`。但在某些情况下,您可能需要获取当前的Win32线程ID - 使用pInvoke调用此函数来获取它:
[DllImport("Kernel32", EntryPoint = "GetCurrentThreadId", ExactSpelling = true)]
public static extern Int32 GetCurrentWin32ThreadId();

首先,您需要保存托管线程ID和Win32线程ID的连接 - 使用将Win32 ID映射到托管线程的字典。

然后,要按其ID查找线程,请使用Process.GetCurrentProcess().Threads迭代进程的线程并找到该ID对应的线程:

foreach (ProcessThread thread in Process.GetCurrentProcess().Threads)
{
     var managedThread = win32ToManagedThread[thread.id];
     if((managedThread.ManagedThreadId == threadId)
     {
         return managedThread;
     }
}

我认为OP是在询问线程的操作系统ID,这与托管线程ID不同。 - Brian Rasmussen
这段代码无法工作:Process.Threads 返回一个 ProcessThread 对象的集合,它与 Thread 不同(也不继承自它):(thread as Thread) 将返回一个空引用。 - Fredrik Mörk
我注意到代码中有一些错误 - 已经修复,请现在再试一下。 - Dror Helper
1
我最终使用了一个字典,将win32 id映射到托管线程。 - Contango

14

在Windows 10下,x64位应用程序的偏移量为0x022C,x32位应用程序的偏移量为0x0160:

public static int GetNativeThreadId(Thread thread)
{
    var f = typeof(Thread).GetField("DONT_USE_InternalThread",
        BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance);

    var pInternalThread = (IntPtr)f.GetValue(thread);
    var nativeId = Marshal.ReadInt32(pInternalThread, (IntPtr.Size == 8) ? 0x022C : 0x0160); // found by analyzing the memory
    return nativeId;
}

1
可在 Windows 7 x64 带有 SP1 上运行。虽然不建议如此做,只建议在临时测试中使用。 - guan boshen
我们不会将其用于临时测试,而是在生产环境中与我们的iplus框架(https://iplus-framework.com)一起用于诊断目的。 - Handball Reporter

7

5

从托管代码中,您可以访问每个托管线程的Thread类型实例。 Thread封装了操作系统线程的概念,在当前CLR中,托管线程和操作系统线程是一对一的对应关系。但是,这是一个实现细节,未来可能会发生变化。

Visual Studio显示的ID实际上是操作系统线程ID。这与几个回复建议的托管线程ID不同。

Thread类型确实包括一个名为DONT_USE_InternalThread的私有IntPtr成员字段,它指向底层的操作系统结构。然而,由于这只是一个实现细节,所以我认为不建议追求此项。而且该名称表明您不应依赖此项。


要使用 GetThreadId,您需要句柄 - 这是从 DONT_USE 字段获取的。 - configurator
我知道,但正如我所说,你不能真正指望托管线程直接映射到操作系统线程,所以我不会指望它。 - Brian Rasmussen
非常感谢您的澄清和问题总结。 但是,如果多个托管线程可以对应一个操作系统线程(正如配置器所述 - 他已经受到感谢),这意味着VS显示的是操作系统线程而不是托管线程。 - LolaRun
@OhrmaZd:是的,VS2005/2008在线程窗口中显示托管线程的操作系统ID。VS2010B2实际上会显示每个线程的操作系统ID和托管ID。 - Brian Rasmussen
@Brian Rasmussen:这才是一个托管线程的标识!感谢您分享您的知识。 - LolaRun

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接