我想了解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个正在等待。我的问题是:
这些线程来自哪里? 我猜这4个等待线程是ThreadPool线程。必须在输入main方法之前创建它们。你能指点我创建那些线程的.Net源代码吗?
我知道我们可以使用ThreadPool.QueueUserWorkItem向ThreadPool发布任务,但ThreadPool如何获取任务/委托?我想有一些东西,比如WinForm UI线程的消息泵,是否有一个后台线程不断检查是否有任何新任务?我也可以看到源代码吗?
修改 感谢托马斯,所以我对ThreadPool的假设/想象是完全错误的。我将用更合适的例子打开另一个问题。
答案 0 :(得分:3)
总而言之,你的所有问题都深入探讨,但你现在似乎并没有足够的知识来理解这一切。
首先,您的代码显示的是所有线程的列表,而不仅仅是.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();
基本上extern
表示它没有在C#中实现,而是在某些本机代码中实现。
ThreadPool如何选择任务/委托?
所有任务都进入队列。再次见Threadpool.cs(撰写本文时第71行):
internal sealed class ThreadPoolWorkQueue
它有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
您可以使用~*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是本机线程,等待某事。
# 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)
在原生视图中,callstack是
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]