Basics
All in all your questions go into the very deep, but you don't seem to have the knowledge to understand all that at the moment.
First of all, what your code shows, is a list of all threads, not only the .NET threads. That's important to know.
Where do these threads come from?
In general they can come from
- .NET
- native code that is loaded into a .NET app
- hooks
- debuggers
- ... possible more.
You can use a debugger and look at the call stacks to see what those threads do (will be covered later).
Can you point me to the .Net source code that creating those threads?
There's one thread that is created by the operating system when running an .EXE file. You'll not find that one in the .NET framework.
It should be possible to find the places for the Finalizer thread and Threadpool threads in the .NET source.
I think one piece of relevant code is in Threadpool.cs at line 1776 to 1780 at the time of writing:
[System.Security.SecurityCritical] // auto-generated
[ResourceExposure(ResourceScope.None)]
[DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
[SuppressUnmanagedCodeSecurity]
internal static extern bool RequestWorkerThread();
Basically the extern
says it's not implemented in C# but in some native code.
how does the ThreadPool pick up a task/delegate?
All tasks go into a queue. See Threadpool.cs again (line 71 at the time of writing):
internal sealed class ThreadPoolWorkQueue
It has an Add()
method and a Remove()
method.
is there one background thread constantly checking if there is any new task?
No. Your code inserts items into the queue and the threadpool worker threads take items from the queue.
can I see the source code?
Same location as above. Would be too much to paste here.
Debugging it
Note that you need a debugger that also displays native threads. Also note that the debugger creates one additional thread in order to enter the application.
You can use the ~
command of WinDbg to see a list of threads:
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
And you can see the call stacks of all threads using ~*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
So thread 0 seems to use the CLR and therefore is likely a .NET thread. "RunMain" seems like that's the main thread.
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
So thread 1 also uses the CLR and is a .NET thread as well.
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
Thread 2 also uses the CLR and FinalizerThread
indicates that this is related to garbage collection.
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
Thread 3 is a native thread, waiting for something.
# 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
Thread 4 is the thread created by the debugger and not visible in your program's output, since it didn't exist at that time.
If you want to focus on .NET only, you need a plugin for .NET. Here you find that there's one thread (running Main()
) and the Finalizer thread.
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)
So in the example given, there's no Threadpool thread.
With Threadpool
With threadpool threads, the output would change to
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)
In the native view, the callstack is
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
So as well, quite obvious that this is a Threadpool thread.
If the thread is currently running some .NET code, you can also make the .NET call stack visible:
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]