如何创建.Net ThreadPool线程?

时间:2018-02-02 20:23:04

标签: 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如何获取任务/委托?我想有一些东西,比如WinForm UI线程的消息泵,是否有一个后台线程不断检查是否有任何新任务?我也可以看到源代码吗?

  3. 修改 感谢托马斯,所以我对ThreadPool的假设/想象是完全错误的。我将用更合适的例子打开另一个问题。

1 个答案:

答案 0 :(得分:3)

基本

总而言之,你的所有问题都深入探讨,但你现在似乎并没有足够的知识来理解这一切。

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

  

这些线程来自哪里?

一般来说,他们可以来自

  • .NET
  • 加载到.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线程。

使用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]