Stu*_*222 2 .net c# clr multithreading threadpool
我试图了解 TheadPool 的工作原理。一个简单的控制台应用程序如下所示:
public static void Main()
{
foreach (ProcessThread thread in Process.GetCurrentProcess().Threads)
{
Console.WriteLine($"thread.ThreadState: {thread.ThreadState}");
}
Console.ReadLine();
}
Run Code Online (Sandbox Code Playgroud)
输出是:
thread.ThreadState: Running
thread.ThreadState: Wait
thread.ThreadState: Wait
thread.ThreadState: Wait
thread.ThreadState: Wait
Run Code Online (Sandbox Code Playgroud)
所以有 5 个线程,1 个正在运行,4 个正在等待。我的问题是:
这些线程从哪里来?我猜这4个等待线程是ThreadPool线程。它们必须在进入我的主要方法之前创建。您能给我指出创建这些线程的 .Net 源代码吗?
我知道我们可以使用 ThreadPool.QueueUserWorkItem 将任务发布到 ThreadPool,但是 ThreadPool 如何获取任务/委托?我猜想有像WinForm UI线程的消息泵之类的东西,是否有一个后台线程不断检查是否有任何新任务?另外可以看看源代码吗?
编辑: 感谢 Thomas,所以我对 ThreadPool 的假设/想象是完全错误的。我将用更合适的例子提出另一个问题。
总而言之,你的问题很深入,但你目前似乎没有足够的知识来理解这一切。
首先,您的代码显示的是所有线程的列表,而不仅仅是 .NET 线程。知道这一点很重要。
这些线程从哪里来?
一般来说,它们可以来自
您可以使用调试器并查看调用堆栈以了解这些线程的作用(稍后将介绍)。
您能给我指出创建这些线程的 .Net 源代码吗?
操作系统在运行 .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();
Run Code Online (Sandbox Code Playgroud)
基本上extern说它不是在 C# 中实现的,而是在一些本机代码中实现的。
ThreadPool 如何获取任务/委托?
所有任务都进入队列。再次参见Threadpool.cs(撰写本文时第 71 行):
internal sealed class ThreadPoolWorkQueue
Run Code Online (Sandbox Code Playgroud)
它有Add()方法,有Remove()方法。
是否有一个后台线程不断检查是否有任何新任务?
不会。您的代码将项目插入队列,线程池工作线程从队列中取出项目。
我可以看一下源代码吗?
与上面相同的位置。在这里粘贴的话太多了。
请注意,您需要一个也显示本机线程的调试器。另请注意,调试器会创建一个附加线程以便进入应用程序。
可以使用~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
Run Code Online (Sandbox Code Playgroud)
您可以使用以下命令查看所有线程的调用堆栈~*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
Run Code Online (Sandbox Code Playgroud)
因此线程 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
Run Code Online (Sandbox Code Playgroud)
因此线程 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
Run Code Online (Sandbox Code Playgroud)
线程 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
Run Code Online (Sandbox Code Playgroud)
线程 3 是一个本机线程,正在等待某些事情。
# 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
Run Code Online (Sandbox Code Playgroud)
线程 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)
Run Code Online (Sandbox Code Playgroud)
因此,在给出的示例中,没有 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)
Run Code Online (Sandbox Code Playgroud)
在本机视图中,调用堆栈是
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
Run Code Online (Sandbox Code Playgroud)
同样,很明显这是一个线程池线程。
如果线程当前正在运行某些 .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]
Run Code Online (Sandbox Code Playgroud)