c# - .Net ThreadPool 线程是如何创建的?

标签 c# .net multithreading clr threadpool

<分区>

我正在尝试了解 TheadPool 的工作原理。像这样的简单控制台应用程序:

public static void Main()
{
    foreach (ProcessThread thread in Process.GetCurrentProcess().Threads)
    {
        Console.WriteLine($"thread.ThreadState: {thread.ThreadState}");
    }    
    Console.ReadLine();
}

输出是:

thread.ThreadState: Running  
thread.ThreadState: Wait  
thread.ThreadState: Wait  
thread.ThreadState: Wait  
thread.ThreadState: Wait 

所以有5个线程,1个运行,4个等待。我的问题是:

  1. 这些线程来自哪里? 我猜这 4 个等待线程是 ThreadPool 线程。它们必须在进入我的 main 方法之前创建。 你能指出创建这些线程的 .Net 源代码吗?

  2. 我知道我们可以使用 ThreadPool.QueueUserWorkItem 将任务发布到 ThreadPool,但是 ThreadPool 如何获取任务/委托(delegate)?我想有一些类似 WinForm UI 线程的消息泵之类的东西,是否有一个后台线程不断检查是否有任何新任务?还能看到源码吗?

编辑: 感谢 Thomas,所以我对 ThreadPool 的假设/想象是完全错误的。我将用更合适的例子打开另一个问题。

最佳答案

基础知识

总而言之,您的所有问题都非常深入,但您目前似乎不具备理解所有这些的知识。

首先,您的代码显示的是所有线程的列表,而不仅仅是 .NET 线程。了解这一点很重要。

Where do these threads come from?

一般他们可以来自

  • .NET
  • 加载到 .NET 应用程序中的 native 代码
  • 钩子(Hook)
  • 调试器
  • ... 可能更多。

您可以使用调试器并查看调用堆栈以查看这些线程执行的操作(稍后将介绍)。

Can you point me to the .Net source code that creating those threads?

在运行 .EXE 文件时,操作系统会创建一个线程。您不会在 .NET 框架中找到它。

应该可以在 .NET 源代码中找到 Finalizer 线程和 Threadpool 线程的位置。

我认为一段相关代码在 Threadpool.cs 中在撰写本文时的第 1776 到 1780 行:

[System.Security.SecurityCritical]  // auto-generated
[ResourceExposure(ResourceScope.None)]
[DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
[SuppressUnmanagedCodeSecurity]
internal static extern bool RequestWorkerThread();

基本上 extern 表示它不是在 C# 中实现的,而是在一些 native 代码中实现的。

how does the ThreadPool pick up a task/delegate?

所有任务都进入一个队列。参见 Threadpool.cs再次(撰写本文时的第 71 行):

internal sealed class ThreadPoolWorkQueue

它有一个 Add() 方法和一个 Remove() 方法。

is there one background thread constantly checking if there is any new task?

没有。您的代码将项目插入队列,线程池工作线程从队列中取出项目。

can I see the source code?

同上。太多了,无法粘贴到这里。

调试

请注意,您需要一个还可以显示 native 线程的调试器。另请注意,调试器会创建一个额外的线程以进入应用程序。

可以使用WinDbg的~命令查看线程列表:

0:004> ~
   0  Id: 223c.650 Suspend: 1 Teb: fffdd000 Unfrozen
   1  Id: 223c.2494 Suspend: 1 Teb: fffda000 Unfrozen
   2  Id: 223c.13b4 Suspend: 1 Teb: fffd7000 Unfrozen
   3  Id: 223c.1edc Suspend: 1 Teb: fffaf000 Unfrozen
.  4  Id: 223c.230c Suspend: 1 Teb: fffac000 Unfrozen

并且可以使用~*k查看所有线程的调用栈:

0:004> ~*k

   0  Id: 223c.650 Suspend: 1 Teb: fffdd000 Unfrozen
 # ChildEBP RetAddr  
00 0034ee80 75ce7b39 KERNEL32!ReadConsoleInternal+0x15
01 0034ef08 75c6f1a2 KERNEL32!ReadConsoleA+0x40
*** WARNING: Unable to verify checksum for C:\Windows\assembly\NativeImages_v4.0.30319_32\mscorlib\22478b54e1cc995a45aafd8e6482de96\mscorlib.ni.dll
02 0034ef50 7190c747 KERNEL32!ReadFileImplementation+0x75
03 0034efc0 720425a3 mscorlib_ni+0x46c747
04 0034efec 720424b2 mscorlib_ni+0xba25a3
05 0034f018 718679c3 mscorlib_ni+0xba24b2
06 0034f030 71867ebf mscorlib_ni+0x3c79c3
07 0034f04c 7217c401 mscorlib_ni+0x3c7ebf
08 0034f05c 71fe7690 mscorlib_ni+0xcdc401
09 0034f064 004a058c mscorlib_ni+0xb47690
WARNING: Frame IP not in any known module. Following frames may be wrong.
0a 0034f0c8 7294eaf6 0x4a058c
0b 0034f0d4 729570c9 clr!CallDescrWorkerInternal+0x34
0c 0034f128 729576f4 clr!CallDescrWorkerWithHandler+0x6b
0d 0034f198 72aeabf1 clr!MethodDescCallSite::CallTargetWorker+0x16a
0e 0034f2c4 72aeace9 clr!RunMain+0x1ad
0f 0034f538 72aeb2eb clr!Assembly::ExecuteMainMethod+0x124
10 0034fa30 72aeb4a1 clr!SystemDomain::ExecuteMainMethod+0x631
11 0034fa88 72aeb3e7 clr!ExecuteEXE+0x4c
12 0034fac8 72a6f7dc clr!_CorExeMainInternal+0xdc
13 0034fb04 7305d6eb clr!_CorExeMain+0x4d
14 0034fb40 730d7f16 mscoreei!_CorExeMain+0x10e
15 0034fb50 730d4de3 MSCOREE!ShellShim__CorExeMain+0x99
16 0034fb58 75c4343d MSCOREE!_CorExeMain_Exported+0x8
17 0034fb64 77ac9832 KERNEL32!BaseThreadInitThunk+0xe
18 0034fba4 77ac9805 ntdll!__RtlUserThreadStart+0x70
19 0034fbbc 00000000 ntdll!_RtlUserThreadStart+0x1b

所以线程 0 似乎使用了 CLR,因此很可能是 .NET 线程。 “RunMain”似乎是主线程。

   1  Id: 223c.2494 Suspend: 1 Teb: fffda000 Unfrozen
 # ChildEBP RetAddr  
00 00aaf7e4 7627171a ntdll!ZwWaitForMultipleObjects+0x15
01 00aaf880 75c419fc KERNELBASE!WaitForMultipleObjectsEx+0x100
02 00aaf8c8 72a6c4eb KERNEL32!WaitForMultipleObjectsExImplementation+0xe0
03 00aaf934 72a6c440 clr!DebuggerRCThread::MainLoop+0x99
04 00aaf964 72a6c36d clr!DebuggerRCThread::ThreadProc+0xd0
05 00aaf990 75c4343d clr!DebuggerRCThread::ThreadProcStatic+0xc4
06 00aaf99c 77ac9832 KERNEL32!BaseThreadInitThunk+0xe
07 00aaf9dc 77ac9805 ntdll!__RtlUserThreadStart+0x70
08 00aaf9f4 00000000 ntdll!_RtlUserThreadStart+0x1b

因此线程 1 也使用 CLR,并且也是 .NET 线程。

   2  Id: 223c.13b4 Suspend: 1 Teb: fffd7000 Unfrozen
 # ChildEBP RetAddr  
00 0446f578 7627171a ntdll!ZwWaitForMultipleObjects+0x15
01 0446f614 75c419fc KERNELBASE!WaitForMultipleObjectsEx+0x100
02 0446f65c 72ad6765 KERNEL32!WaitForMultipleObjectsExImplementation+0xe0
03 0446f68c 72a2d5ce clr!FinalizerThread::WaitForFinalizerEvent+0x8a
04 0446f6bc 72a01e29 clr!FinalizerThread::FinalizerThreadWorker+0x5f
05 0446f6d0 72a01e93 clr!ManagedThreadBase_DispatchInner+0x71
06 0446f774 72a01f60 clr!ManagedThreadBase_DispatchMiddle+0x7e
07 0446f7d0 72aea805 clr!ManagedThreadBase_DispatchOuter+0x5b
08 0446f7f8 72aea8cf clr!ManagedThreadBase::FinalizerBase+0x33
09 0446f834 72a15dd1 clr!FinalizerThread::FinalizerThreadStart+0xd4
0a 0446f8d8 75c4343d clr!Thread::intermediateThreadProc+0x55
0b 0446f8e4 77ac9832 KERNEL32!BaseThreadInitThunk+0xe
0c 0446f924 77ac9805 ntdll!__RtlUserThreadStart+0x70
0d 0446f93c 00000000 ntdll!_RtlUserThreadStart+0x1b

线程 2 也使用了 CLR,FinalizerThread 表明这与垃圾收集有关。

   3  Id: 223c.1edc Suspend: 1 Teb: fffaf000 Unfrozen
 # ChildEBP RetAddr  
00 045bf7fc 77adf69f ntdll!ZwWaitForMultipleObjects+0x15
01 045bf990 75c4343d ntdll!TppWaiterpThread+0x32e
02 045bf99c 77ac9832 KERNEL32!BaseThreadInitThunk+0xe
03 045bf9dc 77ac9805 ntdll!__RtlUserThreadStart+0x70
04 045bf9f4 00000000 ntdll!_RtlUserThreadStart+0x1b

线程 3 是 native 线程,正在等待某事。

#  4  Id: 223c.230c Suspend: 1 Teb: fffac000 Unfrozen
 # ChildEBP RetAddr  
00 04a7fbe0 77b2f306 ntdll!DbgBreakPoint
01 04a7fc10 75c4343d ntdll!DbgUiRemoteBreakin+0x3c
02 04a7fc1c 77ac9832 KERNEL32!BaseThreadInitThunk+0xe
03 04a7fc5c 77ac9805 ntdll!__RtlUserThreadStart+0x70
04 04a7fc74 00000000 ntdll!_RtlUserThreadStart+0x1b

线程 4 是由调试器创建的线程,在程序的输出中不可见,因为它当时不存在。

如果您只想专注于 .NET,则需要一个 .NET 插件。在这里您会发现有一个线程(运行 Main())和 Finalizer 线程。

0:004> .loadby sos clr
0:004> !threads
ThreadCount:      2
UnstartedThread:  0
BackgroundThread: 1
PendingThread:    0
DeadThread:       0
Hosted Runtime:   no
                                                                         Lock  
       ID OSID ThreadOBJ    State GC Mode     GC Alloc Context  Domain   Count Apt Exception
   0    1  650 003e60a8     2a020 Preemptive  0224DC38:00000000 003ad308 1     MTA 
   2    2 13b4 003f2970     2b220 Preemptive  00000000:00000000 003ad308 0     MTA (Finalizer) 

因此在给出的示例中,没有 Threadpool 线程。

使用线程池

对于线程池线程,输出将变为

0:005> !threads
ThreadCount:      4
UnstartedThread:  0
BackgroundThread: 3
PendingThread:    0
DeadThread:       0
Hosted Runtime:   no
                                                                         Lock  
       ID OSID ThreadOBJ    State GC Mode     GC Alloc Context  Domain   Count Apt Exception
   0    1 2384 004460a8     2a020 Preemptive  022F00F0:00000000 0040d308 1     MTA 
   2    2 268c 00452970     2b220 Preemptive  00000000:00000000 0040d308 0     MTA (Finalizer) 
   4    3 2520 0046cad0   1029220 Preemptive  022A8224:00000000 0040d308 0     MTA (Threadpool Worker) 
   6    4 26d4 00473a18   1029220 Preemptive  022A61E4:00000000 0040d308 0     MTA (Threadpool Worker) 

在原生 View 中,调用栈是

0:004> k
 # ChildEBP RetAddr  
00 00e2fa04 762715ce ntdll!NtWaitForSingleObject+0x15
01 00e2fa70 75c41194 KERNELBASE!WaitForSingleObjectEx+0x98
02 00e2fa88 72a02396 KERNEL32!WaitForSingleObjectExImplementation+0x75
03 00e2faec 72a025e7 clr!CLRSemaphore::Wait+0xc0
04 00e2fb28 72a02681 clr!ThreadpoolMgr::UnfairSemaphore::Wait+0x132
05 00e2fb94 72a15dd1 clr!ThreadpoolMgr::WorkerThreadStart+0x389
06 00e2fcb0 75c4343d clr!Thread::intermediateThreadProc+0x55
07 00e2fcbc 77ac9832 KERNEL32!BaseThreadInitThunk+0xe
08 00e2fcfc 77ac9805 ntdll!__RtlUserThreadStart+0x70
09 00e2fd14 00000000 ntdll!_RtlUserThreadStart+0x1b

同样,很明显这是一个 Threadpool 线程。

如果线程当前正在运行一些 .NET 代码,您还可以使 .NET 调用堆栈可见:

0:000> !clrstack
OS Thread Id: 0x2384 (0)
Child SP       IP Call Site
0034f2b0 75ce7ed0 [InlinedCallFrame: 0034f2b0] 
0034f2ac 7190c747 DomainNeutralILStubClass.IL_STUB_PInvoke(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte*, Int32, Int32 ByRef, IntPtr)
0034f2b0 720425a3 [InlinedCallFrame: 0034f2b0] Microsoft.Win32.Win32Native.ReadFile(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte*, Int32, Int32 ByRef, IntPtr)
0034f314 720425a3 System.IO.__ConsoleStream.ReadFileNative(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte[], Int32, Int32, Boolean, Boolean, Int32 ByRef) [f:\dd\ndp\clr\src\BCL\system\io\__consolestream.cs @ 205]
0034f348 720424b2 System.IO.__ConsoleStream.Read(Byte[], Int32, Int32) [f:\dd\ndp\clr\src\BCL\system\io\__consolestream.cs @ 134]
0034f368 718679c3 System.IO.StreamReader.ReadBuffer() [f:\dd\ndp\clr\src\BCL\system\io\streamreader.cs @ 595]
0034f378 71867ebf System.IO.StreamReader.ReadLine() [f:\dd\ndp\clr\src\BCL\system\io\streamreader.cs @ 748]
0034f394 7217c401 System.IO.TextReader+SyncTextReader.ReadLine() [f:\dd\ndp\clr\src\BCL\system\io\textreader.cs @ 363]
0034f3a4 71fe7690 System.Console.ReadLine() [f:\dd\ndp\clr\src\BCL\system\console.cs @ 1984]
0034f3ac 003a0669 ConsoleApp2.Program.Main(System.String[]) [C:\Users\For example John\Documents\Visual Studio 2017\Projects\ConsoleApp2\Program.cs @ 16]
0034f588 7294eaf6 [GCFrame: 0034f588] 

关于c# - .Net ThreadPool 线程是如何创建的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48590349/

相关文章:

c# - asp.net core项目发布后iis立即关闭

c# - 从 plink 获取重定向输出

Python 应用程序级文档/记录锁定(例如,对于 MongoDB)

c# - 在C#的 “getTickFrequency”中相当于来自OpenCV的 “Emgu”?

c# - .net User Secrets 在 Windows 10 上不起作用

c# - 使用 protobuf-net 反序列化完全未知的对象

android - 从 Android 的 .NET WebService 中保存字节

java - 在主线程中运行的 ScheduledExecutorService

android - Ice Cream Sandwich 上的线程

c# - 当 iis 是主机时,wcf 应用程序上的 iis 是否调用了 Global.asax 的 Application_Start?