1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
5 /*=============================================================================
9 ** Purpose: Class for creating and managing a threadpool
12 =============================================================================*/
14 using System.Collections.Concurrent;
15 using System.Collections.Generic;
16 using System.Diagnostics;
17 using System.Diagnostics.CodeAnalysis;
18 using System.Diagnostics.Tracing;
19 using System.Runtime.CompilerServices;
20 using System.Runtime.ConstrainedExecution;
21 using System.Runtime.InteropServices;
22 using System.Security;
23 using Microsoft.Win32;
25 namespace System.Threading
27 internal static class ThreadPoolGlobals
29 //Per-appDomain quantum (in ms) for which the thread keeps processing
30 //requests in the current domain.
31 public const uint TP_QUANTUM = 30U;
33 public static readonly int processorCount = Environment.ProcessorCount;
35 public static volatile bool vmTpInitialized;
36 public static bool enableWorkerTracking;
38 public static readonly ThreadPoolWorkQueue workQueue = new ThreadPoolWorkQueue();
41 [StructLayout(LayoutKind.Sequential)] // enforce layout so that padding reduces false sharing
42 internal sealed class ThreadPoolWorkQueue
44 internal static class WorkStealingQueueList
46 private static volatile WorkStealingQueue[] _queues = new WorkStealingQueue[0];
48 public static WorkStealingQueue[] Queues => _queues;
50 public static void Add(WorkStealingQueue queue)
52 Debug.Assert(queue != null);
55 WorkStealingQueue[] oldQueues = _queues;
56 Debug.Assert(Array.IndexOf(oldQueues, queue) == -1);
58 var newQueues = new WorkStealingQueue[oldQueues.Length + 1];
59 Array.Copy(oldQueues, 0, newQueues, 0, oldQueues.Length);
60 newQueues[newQueues.Length - 1] = queue;
61 if (Interlocked.CompareExchange(ref _queues, newQueues, oldQueues) == oldQueues)
68 public static void Remove(WorkStealingQueue queue)
70 Debug.Assert(queue != null);
73 WorkStealingQueue[] oldQueues = _queues;
74 if (oldQueues.Length == 0)
79 int pos = Array.IndexOf(oldQueues, queue);
82 Debug.Fail("Should have found the queue");
86 var newQueues = new WorkStealingQueue[oldQueues.Length - 1];
89 Array.Copy(oldQueues, 1, newQueues, 0, newQueues.Length);
91 else if (pos == oldQueues.Length - 1)
93 Array.Copy(oldQueues, 0, newQueues, 0, newQueues.Length);
97 Array.Copy(oldQueues, 0, newQueues, 0, pos);
98 Array.Copy(oldQueues, pos + 1, newQueues, pos, newQueues.Length - pos);
101 if (Interlocked.CompareExchange(ref _queues, newQueues, oldQueues) == oldQueues)
109 internal sealed class WorkStealingQueue
111 private const int INITIAL_SIZE = 32;
112 internal volatile IThreadPoolWorkItem[] m_array = new IThreadPoolWorkItem[INITIAL_SIZE];
113 private volatile int m_mask = INITIAL_SIZE - 1;
116 // in debug builds, start at the end so we exercise the index reset logic.
117 private const int START_INDEX = int.MaxValue;
119 private const int START_INDEX = 0;
122 private volatile int m_headIndex = START_INDEX;
123 private volatile int m_tailIndex = START_INDEX;
125 private SpinLock m_foreignLock = new SpinLock(enableThreadOwnerTracking: false);
127 public void LocalPush(IThreadPoolWorkItem obj)
129 int tail = m_tailIndex;
131 // We're going to increment the tail; if we'll overflow, then we need to reset our counts
132 if (tail == int.MaxValue)
134 bool lockTaken = false;
137 m_foreignLock.Enter(ref lockTaken);
139 if (m_tailIndex == int.MaxValue)
142 // Rather than resetting to zero, we'll just mask off the bits we don't care about.
143 // This way we don't need to rearrange the items already in the queue; they'll be found
144 // correctly exactly where they are. One subtlety here is that we need to make sure that
145 // if head is currently < tail, it remains that way. This happens to just fall out from
146 // the bit-masking, because we only do this if tail == int.MaxValue, meaning that all
147 // bits are set, so all of the bits we're keeping will also be set. Thus it's impossible
148 // for the head to end up > than the tail, since you can't set any more bits than all of
151 m_headIndex = m_headIndex & m_mask;
152 m_tailIndex = tail = m_tailIndex & m_mask;
153 Debug.Assert(m_headIndex <= m_tailIndex);
159 m_foreignLock.Exit(useMemoryBarrier: true);
163 // When there are at least 2 elements' worth of space, we can take the fast path.
164 if (tail < m_headIndex + m_mask)
166 Volatile.Write(ref m_array[tail & m_mask], obj);
167 m_tailIndex = tail + 1;
171 // We need to contend with foreign pops, so we lock.
172 bool lockTaken = false;
175 m_foreignLock.Enter(ref lockTaken);
177 int head = m_headIndex;
178 int count = m_tailIndex - m_headIndex;
180 // If there is still space (one left), just add the element.
183 // We're full; expand the queue by doubling its size.
184 var newArray = new IThreadPoolWorkItem[m_array.Length << 1];
185 for (int i = 0; i < m_array.Length; i++)
186 newArray[i] = m_array[(i + head) & m_mask];
188 // Reset the field values, incl. the mask.
191 m_tailIndex = tail = count;
192 m_mask = (m_mask << 1) | 1;
195 Volatile.Write(ref m_array[tail & m_mask], obj);
196 m_tailIndex = tail + 1;
201 m_foreignLock.Exit(useMemoryBarrier: false);
206 [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "Reviewed for thread safety")]
207 public bool LocalFindAndPop(IThreadPoolWorkItem obj)
209 // Fast path: check the tail. If equal, we can skip the lock.
210 if (m_array[(m_tailIndex - 1) & m_mask] == obj)
212 IThreadPoolWorkItem unused = LocalPop();
213 Debug.Assert(unused == null || unused == obj);
214 return unused != null;
217 // Else, do an O(N) search for the work item. The theory of work stealing and our
218 // inlining logic is that most waits will happen on recently queued work. And
219 // since recently queued work will be close to the tail end (which is where we
220 // begin our search), we will likely find it quickly. In the worst case, we
221 // will traverse the whole local queue; this is typically not going to be a
222 // problem (although degenerate cases are clearly an issue) because local work
223 // queues tend to be somewhat shallow in length, and because if we fail to find
224 // the work item, we are about to block anyway (which is very expensive).
225 for (int i = m_tailIndex - 2; i >= m_headIndex; i--)
227 if (m_array[i & m_mask] == obj)
229 // If we found the element, block out steals to avoid interference.
230 bool lockTaken = false;
233 m_foreignLock.Enter(ref lockTaken);
235 // If we encountered a race condition, bail.
236 if (m_array[i & m_mask] == null)
239 // Otherwise, null out the element.
240 Volatile.Write(ref m_array[i & m_mask], null);
242 // And then check to see if we can fix up the indexes (if we're at
243 // the edge). If we can't, we just leave nulls in the array and they'll
244 // get filtered out eventually (but may lead to superflous resizing).
245 if (i == m_tailIndex)
247 else if (i == m_headIndex)
255 m_foreignLock.Exit(useMemoryBarrier: false);
263 public IThreadPoolWorkItem LocalPop() => m_headIndex < m_tailIndex ? LocalPopCore() : null;
265 [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "Reviewed for thread safety")]
266 private IThreadPoolWorkItem LocalPopCore()
270 int tail = m_tailIndex;
271 if (m_headIndex >= tail)
276 // Decrement the tail using a fence to ensure subsequent read doesn't come before.
278 Interlocked.Exchange(ref m_tailIndex, tail);
280 // If there is no interaction with a take, we can head down the fast path.
281 if (m_headIndex <= tail)
283 int idx = tail & m_mask;
284 IThreadPoolWorkItem obj = Volatile.Read(ref m_array[idx]);
286 // Check for nulls in the array.
287 if (obj == null) continue;
294 // Interaction with takes: 0 or 1 elements left.
295 bool lockTaken = false;
298 m_foreignLock.Enter(ref lockTaken);
300 if (m_headIndex <= tail)
302 // Element still available. Take it.
303 int idx = tail & m_mask;
304 IThreadPoolWorkItem obj = Volatile.Read(ref m_array[idx]);
306 // Check for nulls in the array.
307 if (obj == null) continue;
314 // If we encountered a race condition and element was stolen, restore the tail.
315 m_tailIndex = tail + 1;
322 m_foreignLock.Exit(useMemoryBarrier: false);
328 public bool CanSteal => m_headIndex < m_tailIndex;
330 public IThreadPoolWorkItem TrySteal(ref bool missedSteal)
339 m_foreignLock.TryEnter(ref taken);
342 // Increment head, and ensure read of tail doesn't move before it (fence).
343 int head = m_headIndex;
344 Interlocked.Exchange(ref m_headIndex, head + 1);
346 if (head < m_tailIndex)
348 int idx = head & m_mask;
349 IThreadPoolWorkItem obj = Volatile.Read(ref m_array[idx]);
351 // Check for nulls in the array.
352 if (obj == null) continue;
359 // Failed, restore head.
367 m_foreignLock.Exit(useMemoryBarrier: false);
378 internal bool loggingEnabled;
379 internal readonly ConcurrentQueue<IThreadPoolWorkItem> workItems = new ConcurrentQueue<IThreadPoolWorkItem>();
381 private Internal.PaddingFor32 pad1;
383 private volatile int numOutstandingThreadRequests = 0;
385 private Internal.PaddingFor32 pad2;
387 public ThreadPoolWorkQueue()
389 loggingEnabled = FrameworkEventSource.Log.IsEnabled(EventLevel.Verbose, FrameworkEventSource.Keywords.ThreadPool | FrameworkEventSource.Keywords.ThreadTransfer);
392 public ThreadPoolWorkQueueThreadLocals EnsureCurrentThreadHasQueue() =>
393 ThreadPoolWorkQueueThreadLocals.threadLocals ??
394 (ThreadPoolWorkQueueThreadLocals.threadLocals = new ThreadPoolWorkQueueThreadLocals(this));
396 internal void EnsureThreadRequested()
399 // If we have not yet requested #procs threads from the VM, then request a new thread
402 // Note that there is a separate count in the VM which will also be incremented in this case,
403 // which is handled by RequestWorkerThread.
405 int count = numOutstandingThreadRequests;
406 while (count < ThreadPoolGlobals.processorCount)
408 int prev = Interlocked.CompareExchange(ref numOutstandingThreadRequests, count + 1, count);
411 ThreadPool.RequestWorkerThread();
418 internal void MarkThreadRequestSatisfied()
421 // The VM has called us, so one of our outstanding thread requests has been satisfied.
422 // Decrement the count so that future calls to EnsureThreadRequested will succeed.
423 // Note that there is a separate count in the VM which has already been decremented by the VM
424 // by the time we reach this point.
426 int count = numOutstandingThreadRequests;
429 int prev = Interlocked.CompareExchange(ref numOutstandingThreadRequests, count - 1, count);
438 public void Enqueue(IThreadPoolWorkItem callback, bool forceGlobal)
441 System.Diagnostics.Tracing.FrameworkEventSource.Log.ThreadPoolEnqueueWorkObject(callback);
443 ThreadPoolWorkQueueThreadLocals tl = null;
445 tl = ThreadPoolWorkQueueThreadLocals.threadLocals;
449 tl.workStealingQueue.LocalPush(callback);
453 workItems.Enqueue(callback);
456 EnsureThreadRequested();
459 internal bool LocalFindAndPop(IThreadPoolWorkItem callback)
461 ThreadPoolWorkQueueThreadLocals tl = ThreadPoolWorkQueueThreadLocals.threadLocals;
462 return tl != null && tl.workStealingQueue.LocalFindAndPop(callback);
465 public IThreadPoolWorkItem Dequeue(ThreadPoolWorkQueueThreadLocals tl, ref bool missedSteal)
467 WorkStealingQueue localWsq = tl.workStealingQueue;
468 IThreadPoolWorkItem callback;
470 if ((callback = localWsq.LocalPop()) == null && // first try the local queue
471 !workItems.TryDequeue(out callback)) // then try the global queue
473 // finally try to steal from another thread's local queue
474 WorkStealingQueue[] queues = WorkStealingQueueList.Queues;
475 int c = queues.Length;
476 Debug.Assert(c > 0, "There must at least be a queue for this thread.");
477 int maxIndex = c - 1;
478 int i = tl.random.Next(c);
481 i = (i < maxIndex) ? i + 1 : 0;
482 WorkStealingQueue otherQueue = queues[i];
483 if (otherQueue != localWsq && otherQueue.CanSteal)
485 callback = otherQueue.TrySteal(ref missedSteal);
486 if (callback != null)
498 internal static bool Dispatch()
500 var workQueue = ThreadPoolGlobals.workQueue;
502 // The clock is ticking! We have ThreadPoolGlobals.TP_QUANTUM milliseconds to get some work done, and then
503 // we need to return to the VM.
505 int quantumStartTime = Environment.TickCount;
508 // Update our records to indicate that an outstanding request for a thread has now been fulfilled.
509 // From this point on, we are responsible for requesting another thread if we stop working for any
510 // reason, and we believe there might still be work in the queue.
512 // Note that if this thread is aborted before we get a chance to request another one, the VM will
513 // record a thread request on our behalf. So we don't need to worry about getting aborted right here.
515 workQueue.MarkThreadRequestSatisfied();
517 // Has the desire for logging changed since the last time we entered?
518 workQueue.loggingEnabled = FrameworkEventSource.Log.IsEnabled(EventLevel.Verbose, FrameworkEventSource.Keywords.ThreadPool | FrameworkEventSource.Keywords.ThreadTransfer);
521 // Assume that we're going to need another thread if this one returns to the VM. We'll set this to
522 // false later, but only if we're absolutely certain that the queue is empty.
524 bool needAnotherThread = true;
525 IThreadPoolWorkItem workItem = null;
529 // Set up our thread-local data
531 ThreadPoolWorkQueueThreadLocals tl = workQueue.EnsureCurrentThreadHasQueue();
534 // Loop until our quantum expires.
536 while ((Environment.TickCount - quantumStartTime) < ThreadPoolGlobals.TP_QUANTUM)
538 bool missedSteal = false;
539 workItem = workQueue.Dequeue(tl, ref missedSteal);
541 if (workItem == null)
544 // No work. We're going to return to the VM once we leave this protected region.
545 // If we missed a steal, though, there may be more work in the queue.
546 // Instead of looping around and trying again, we'll just request another thread. This way
547 // we won't starve other AppDomains while we spin trying to get locks, and hopefully the thread
548 // that owns the contended work-stealing queue will pick up its own workitems in the meantime,
549 // which will be more efficient than this thread doing it anyway.
551 needAnotherThread = missedSteal;
553 // Tell the VM we're returning normally, not because Hill Climbing asked us to return.
557 if (workQueue.loggingEnabled)
558 System.Diagnostics.Tracing.FrameworkEventSource.Log.ThreadPoolDequeueWorkObject(workItem);
561 // If we found work, there may be more work. Ask for another thread so that the other work can be processed
562 // in parallel. Note that this will only ask for a max of #procs threads, so it's safe to call it for every dequeue.
564 workQueue.EnsureThreadRequested();
567 // Execute the workitem outside of any finally blocks, so that it can be aborted if needed.
569 if (ThreadPoolGlobals.enableWorkerTracking)
571 bool reportedStatus = false;
574 ThreadPool.ReportThreadStatus(isWorking: true);
575 reportedStatus = true;
576 workItem.ExecuteWorkItem();
581 ThreadPool.ReportThreadStatus(isWorking: false);
586 workItem.ExecuteWorkItem();
591 // Notify the VM that we executed this workitem. This is also our opportunity to ask whether Hill Climbing wants
592 // us to return the thread to the pool or not.
594 if (!ThreadPool.NotifyWorkItemComplete())
598 // If we get here, it's because our quantum expired. Tell the VM we're returning normally.
601 catch (ThreadAbortException tae)
604 // This is here to catch the case where this thread is aborted between the time we exit the finally block in the dispatch
605 // loop, and the time we execute the work item. QueueUserWorkItemCallback uses this to update its accounting of whether
606 // it was executed or not (in debug builds only). Task uses this to communicate the ThreadAbortException to anyone
607 // who waits for the task to complete.
609 workItem?.MarkAborted(tae);
612 // In this case, the VM is going to request another thread on our behalf. No need to do it twice.
614 needAnotherThread = false;
615 // throw; //no need to explicitly rethrow a ThreadAbortException, and doing so causes allocations on amd64.
620 // If we are exiting for any reason other than that the queue is definitely empty, ask for another
621 // thread to pick up where we left off.
623 if (needAnotherThread)
624 workQueue.EnsureThreadRequested();
627 // we can never reach this point, but the C# compiler doesn't know that, because it doesn't know the ThreadAbortException will be reraised above.
628 Debug.Fail("Should never reach this point");
633 // Simple random number generator. We don't need great randomness, we just need a little and for it to be fast.
634 internal struct FastRandom // xorshift prng
636 private uint _w, _x, _y, _z;
638 public FastRandom(int seed)
646 public int Next(int maxValue)
648 Debug.Assert(maxValue > 0);
650 uint t = _x ^ (_x << 11);
651 _x = _y; _y = _z; _z = _w;
652 _w = _w ^ (_w >> 19) ^ (t ^ (t >> 8));
654 return (int)(_w % (uint)maxValue);
658 // Holds a WorkStealingQueue, and remmoves it from the list when this object is no longer referened.
659 internal sealed class ThreadPoolWorkQueueThreadLocals
662 public static ThreadPoolWorkQueueThreadLocals threadLocals;
664 public readonly ThreadPoolWorkQueue workQueue;
665 public readonly ThreadPoolWorkQueue.WorkStealingQueue workStealingQueue;
666 public FastRandom random = new FastRandom(Thread.CurrentThread.ManagedThreadId); // mutable struct, do not copy or make readonly
668 public ThreadPoolWorkQueueThreadLocals(ThreadPoolWorkQueue tpq)
671 workStealingQueue = new ThreadPoolWorkQueue.WorkStealingQueue();
672 ThreadPoolWorkQueue.WorkStealingQueueList.Add(workStealingQueue);
675 private void CleanUp()
677 if (null != workStealingQueue)
679 if (null != workQueue)
681 IThreadPoolWorkItem cb;
682 while ((cb = workStealingQueue.LocalPop()) != null)
684 Debug.Assert(null != cb);
685 workQueue.Enqueue(cb, forceGlobal: true);
689 ThreadPoolWorkQueue.WorkStealingQueueList.Remove(workStealingQueue);
693 ~ThreadPoolWorkQueueThreadLocals()
695 // Since the purpose of calling CleanUp is to transfer any pending workitems into the global
696 // queue so that they will be executed by another thread, there's no point in doing this cleanup
697 // if we're in the process of shutting down or unloading the AD. In those cases, the work won't
698 // execute anyway. And there are subtle race conditions involved there that would lead us to do the wrong
699 // thing anyway. So we'll only clean up if this is a "normal" finalization.
700 if (!(Environment.HasShutdownStarted || AppDomain.CurrentDomain.IsFinalizingForUnload()))
705 internal sealed class RegisteredWaitHandleSafe : CriticalFinalizerObject
707 private static IntPtr InvalidHandle => Win32Native.INVALID_HANDLE_VALUE;
708 private IntPtr registeredWaitHandle = InvalidHandle;
709 private WaitHandle m_internalWaitObject;
710 private bool bReleaseNeeded = false;
711 private volatile int m_lock = 0;
713 internal IntPtr GetHandle() => registeredWaitHandle;
715 internal void SetHandle(IntPtr handle)
717 registeredWaitHandle = handle;
720 internal void SetWaitObject(WaitHandle waitObject)
722 // needed for DangerousAddRef
723 RuntimeHelpers.PrepareConstrainedRegions();
725 m_internalWaitObject = waitObject;
726 if (waitObject != null)
728 m_internalWaitObject.SafeWaitHandle.DangerousAddRef(ref bReleaseNeeded);
732 internal bool Unregister(
733 WaitHandle waitObject // object to be notified when all callbacks to delegates have completed
737 // needed for DangerousRelease
738 RuntimeHelpers.PrepareConstrainedRegions();
740 // lock(this) cannot be used reliably in Cer since thin lock could be
741 // promoted to syncblock and that is not a guaranteed operation
742 bool bLockTaken = false;
745 if (Interlocked.CompareExchange(ref m_lock, 1, 0) == 0)
752 result = UnregisterWaitNative(GetHandle(), waitObject == null ? null : waitObject.SafeWaitHandle);
757 m_internalWaitObject.SafeWaitHandle.DangerousRelease();
758 bReleaseNeeded = false;
760 // if result not true don't release/suppress here so finalizer can make another attempt
761 SetHandle(InvalidHandle);
762 m_internalWaitObject = null;
763 GC.SuppressFinalize(this);
772 Thread.SpinWait(1); // yield to processor
779 private bool ValidHandle() =>
780 registeredWaitHandle != InvalidHandle && registeredWaitHandle != IntPtr.Zero;
782 ~RegisteredWaitHandleSafe()
784 // if the app has already unregistered the wait, there is nothing to cleanup
785 // we can detect this by checking the handle. Normally, there is no race condition here
786 // so no need to protect reading of handle. However, if this object gets
787 // resurrected and then someone does an unregister, it would introduce a race condition
789 // PrepareConstrainedRegions call not needed since finalizer already in Cer
791 // lock(this) cannot be used reliably even in Cer since thin lock could be
792 // promoted to syncblock and that is not a guaranteed operation
794 // Note that we will not "spin" to get this lock. We make only a single attempt;
795 // if we can't get the lock, it means some other thread is in the middle of a call
796 // to Unregister, which will do the work of the finalizer anyway.
798 // Further, it's actually critical that we *not* wait for the lock here, because
799 // the other thread that's in the middle of Unregister may be suspended for shutdown.
800 // Then, during the live-object finalization phase of shutdown, this thread would
801 // end up spinning forever, as the other thread would never release the lock.
802 // This will result in a "leak" of sorts (since the handle will not be cleaned up)
803 // but the process is exiting anyway.
805 // During AD-unload, we don�t finalize live objects until all threads have been
806 // aborted out of the AD. Since these locked regions are CERs, we won�t abort them
807 // while the lock is held. So there should be no leak on AD-unload.
809 if (Interlocked.CompareExchange(ref m_lock, 1, 0) == 0)
815 WaitHandleCleanupNative(registeredWaitHandle);
818 m_internalWaitObject.SafeWaitHandle.DangerousRelease();
819 bReleaseNeeded = false;
821 SetHandle(InvalidHandle);
822 m_internalWaitObject = null;
832 [MethodImplAttribute(MethodImplOptions.InternalCall)]
833 private static extern void WaitHandleCleanupNative(IntPtr handle);
835 [MethodImplAttribute(MethodImplOptions.InternalCall)]
836 private static extern bool UnregisterWaitNative(IntPtr handle, SafeHandle waitObject);
839 public sealed class RegisteredWaitHandle : MarshalByRefObject
841 private readonly RegisteredWaitHandleSafe internalRegisteredWait;
843 internal RegisteredWaitHandle()
845 internalRegisteredWait = new RegisteredWaitHandleSafe();
848 internal void SetHandle(IntPtr handle)
850 internalRegisteredWait.SetHandle(handle);
853 internal void SetWaitObject(WaitHandle waitObject)
855 internalRegisteredWait.SetWaitObject(waitObject);
858 // This is the only public method on this class
859 public bool Unregister(
860 WaitHandle waitObject // object to be notified when all callbacks to delegates have completed
863 return internalRegisteredWait.Unregister(waitObject);
867 public delegate void WaitCallback(Object state);
869 public delegate void WaitOrTimerCallback(Object state, bool timedOut); // signalled or timed out
872 // This type is necessary because VS 2010's debugger looks for a method named _ThreadPoolWaitCallbacck.PerformWaitCallback
873 // on the stack to determine if a thread is a ThreadPool thread or not. We have a better way to do this for .NET 4.5, but
874 // still need to maintain compatibility with VS 2010. When compat with VS 2010 is no longer an issue, this type may be
877 internal static class _ThreadPoolWaitCallback
879 internal static bool PerformWaitCallback() => ThreadPoolWorkQueue.Dispatch();
883 // Interface to something that can be queued to the TP. This is implemented by
884 // QueueUserWorkItemCallback, Task, and potentially other internal types.
885 // For example, SemaphoreSlim represents callbacks using its own type that
886 // implements IThreadPoolWorkItem.
888 // If we decide to expose some of the workstealing
889 // stuff, this is NOT the thing we want to expose to the public.
891 internal interface IThreadPoolWorkItem
893 void ExecuteWorkItem();
894 void MarkAborted(ThreadAbortException tae);
897 internal sealed class QueueUserWorkItemCallback : IThreadPoolWorkItem
899 private WaitCallback _callback;
900 private readonly ExecutionContext _context;
901 private readonly Object _state;
904 private volatile int executed;
906 ~QueueUserWorkItemCallback()
909 executed != 0 || Environment.HasShutdownStarted || AppDomain.CurrentDomain.IsFinalizingForUnload(),
910 "A QueueUserWorkItemCallback was never called!");
913 private void MarkExecuted(bool aborted)
915 GC.SuppressFinalize(this);
917 0 == Interlocked.Exchange(ref executed, 1) || aborted,
918 "A QueueUserWorkItemCallback was called twice!");
922 internal QueueUserWorkItemCallback(WaitCallback waitCallback, Object stateObj, ExecutionContext ec)
924 _callback = waitCallback;
929 void IThreadPoolWorkItem.ExecuteWorkItem()
932 MarkExecuted(aborted: false);
934 // call directly if it is an unsafe call OR EC flow is suppressed
935 ExecutionContext context = _context;
938 WaitCallback cb = _callback;
944 ExecutionContext.RunInternal(context, ccb, this);
948 void IThreadPoolWorkItem.MarkAborted(ThreadAbortException tae)
951 // this workitem didn't execute because we got a ThreadAbortException prior to the call to ExecuteWorkItem.
952 // This counts as being executed for our purposes.
953 MarkExecuted(aborted: true);
957 internal static readonly ContextCallback ccb = new ContextCallback(WaitCallback_Context);
959 private static void WaitCallback_Context(Object state)
961 QueueUserWorkItemCallback obj = (QueueUserWorkItemCallback)state;
962 WaitCallback wc = obj._callback;
963 Debug.Assert(null != wc);
968 internal sealed class QueueUserWorkItemCallbackDefaultContext : IThreadPoolWorkItem
970 private WaitCallback callback;
971 private readonly Object state;
974 private volatile int executed;
976 ~QueueUserWorkItemCallbackDefaultContext()
979 executed != 0 || Environment.HasShutdownStarted || AppDomain.CurrentDomain.IsFinalizingForUnload(),
980 "A QueueUserWorkItemCallbackDefaultContext was never called!");
983 private void MarkExecuted(bool aborted)
985 GC.SuppressFinalize(this);
987 0 == Interlocked.Exchange(ref executed, 1) || aborted,
988 "A QueueUserWorkItemCallbackDefaultContext was called twice!");
992 internal QueueUserWorkItemCallbackDefaultContext(WaitCallback waitCallback, Object stateObj)
994 callback = waitCallback;
998 void IThreadPoolWorkItem.ExecuteWorkItem()
1001 MarkExecuted(aborted: false);
1003 // null executionContext on RunInternal is Default context
1004 ExecutionContext.RunInternal(executionContext: null, ccb, this);
1007 void IThreadPoolWorkItem.MarkAborted(ThreadAbortException tae)
1010 // this workitem didn't execute because we got a ThreadAbortException prior to the call to ExecuteWorkItem.
1011 // This counts as being executed for our purposes.
1012 MarkExecuted(aborted: true);
1016 internal static readonly ContextCallback ccb = new ContextCallback(WaitCallback_Context);
1018 private static void WaitCallback_Context(Object state)
1020 QueueUserWorkItemCallbackDefaultContext obj = (QueueUserWorkItemCallbackDefaultContext)state;
1021 WaitCallback wc = obj.callback;
1022 Debug.Assert(null != wc);
1023 obj.callback = null;
1028 internal class _ThreadPoolWaitOrTimerCallback
1030 private WaitOrTimerCallback _waitOrTimerCallback;
1031 private ExecutionContext _executionContext;
1032 private Object _state;
1033 private static readonly ContextCallback _ccbt = new ContextCallback(WaitOrTimerCallback_Context_t);
1034 private static readonly ContextCallback _ccbf = new ContextCallback(WaitOrTimerCallback_Context_f);
1036 internal _ThreadPoolWaitOrTimerCallback(WaitOrTimerCallback waitOrTimerCallback, Object state, bool compressStack)
1038 _waitOrTimerCallback = waitOrTimerCallback;
1043 // capture the exection context
1044 _executionContext = ExecutionContext.Capture();
1048 private static void WaitOrTimerCallback_Context_t(Object state) =>
1049 WaitOrTimerCallback_Context(state, timedOut: true);
1051 private static void WaitOrTimerCallback_Context_f(Object state) =>
1052 WaitOrTimerCallback_Context(state, timedOut: false);
1054 private static void WaitOrTimerCallback_Context(Object state, bool timedOut)
1056 _ThreadPoolWaitOrTimerCallback helper = (_ThreadPoolWaitOrTimerCallback)state;
1057 helper._waitOrTimerCallback(helper._state, timedOut);
1061 internal static void PerformWaitOrTimerCallback(Object state, bool timedOut)
1063 _ThreadPoolWaitOrTimerCallback helper = (_ThreadPoolWaitOrTimerCallback)state;
1064 Debug.Assert(helper != null, "Null state passed to PerformWaitOrTimerCallback!");
1065 // call directly if it is an unsafe call OR EC flow is suppressed
1066 ExecutionContext context = helper._executionContext;
1067 if (context == null)
1069 WaitOrTimerCallback callback = helper._waitOrTimerCallback;
1070 callback(helper._state, timedOut);
1074 ExecutionContext.Run(context, timedOut ? _ccbt : _ccbf, helper);
1079 [CLSCompliant(false)]
1080 unsafe public delegate void IOCompletionCallback(uint errorCode, // Error code
1081 uint numBytes, // No. of bytes transferred
1082 NativeOverlapped* pOVERLAP // ptr to OVERLAP structure
1085 public static class ThreadPool
1087 public static bool SetMaxThreads(int workerThreads, int completionPortThreads)
1089 return SetMaxThreadsNative(workerThreads, completionPortThreads);
1092 public static void GetMaxThreads(out int workerThreads, out int completionPortThreads)
1094 GetMaxThreadsNative(out workerThreads, out completionPortThreads);
1097 public static bool SetMinThreads(int workerThreads, int completionPortThreads)
1099 return SetMinThreadsNative(workerThreads, completionPortThreads);
1102 public static void GetMinThreads(out int workerThreads, out int completionPortThreads)
1104 GetMinThreadsNative(out workerThreads, out completionPortThreads);
1107 public static void GetAvailableThreads(out int workerThreads, out int completionPortThreads)
1109 GetAvailableThreadsNative(out workerThreads, out completionPortThreads);
1112 [CLSCompliant(false)]
1113 [System.Security.DynamicSecurityMethod] // Methods containing StackCrawlMark local var has to be marked DynamicSecurityMethod
1114 public static RegisteredWaitHandle RegisterWaitForSingleObject( // throws RegisterWaitException
1115 WaitHandle waitObject,
1116 WaitOrTimerCallback callBack,
1118 uint millisecondsTimeOutInterval,
1119 bool executeOnlyOnce // NOTE: we do not allow other options that allow the callback to be queued as an APC
1122 StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
1123 return RegisterWaitForSingleObject(waitObject, callBack, state, millisecondsTimeOutInterval, executeOnlyOnce, ref stackMark, true);
1126 [CLSCompliant(false)]
1127 [System.Security.DynamicSecurityMethod] // Methods containing StackCrawlMark local var has to be marked DynamicSecurityMethod
1128 public static RegisteredWaitHandle UnsafeRegisterWaitForSingleObject( // throws RegisterWaitException
1129 WaitHandle waitObject,
1130 WaitOrTimerCallback callBack,
1132 uint millisecondsTimeOutInterval,
1133 bool executeOnlyOnce // NOTE: we do not allow other options that allow the callback to be queued as an APC
1136 StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
1137 return RegisterWaitForSingleObject(waitObject, callBack, state, millisecondsTimeOutInterval, executeOnlyOnce, ref stackMark, false);
1141 private static RegisteredWaitHandle RegisterWaitForSingleObject( // throws RegisterWaitException
1142 WaitHandle waitObject,
1143 WaitOrTimerCallback callBack,
1145 uint millisecondsTimeOutInterval,
1146 bool executeOnlyOnce, // NOTE: we do not allow other options that allow the callback to be queued as an APC
1147 ref StackCrawlMark stackMark,
1151 RegisteredWaitHandle registeredWaitHandle = new RegisteredWaitHandle();
1153 if (callBack != null)
1155 _ThreadPoolWaitOrTimerCallback callBackHelper = new _ThreadPoolWaitOrTimerCallback(callBack, state, compressStack);
1156 state = (Object)callBackHelper;
1157 // call SetWaitObject before native call so that waitObject won't be closed before threadpoolmgr registration
1158 // this could occur if callback were to fire before SetWaitObject does its addref
1159 registeredWaitHandle.SetWaitObject(waitObject);
1160 IntPtr nativeRegisteredWaitHandle = RegisterWaitForSingleObjectNative(waitObject,
1162 millisecondsTimeOutInterval,
1164 registeredWaitHandle,
1167 registeredWaitHandle.SetHandle(nativeRegisteredWaitHandle);
1171 throw new ArgumentNullException(nameof(WaitOrTimerCallback));
1173 return registeredWaitHandle;
1177 [System.Security.DynamicSecurityMethod] // Methods containing StackCrawlMark local var has to be marked DynamicSecurityMethod
1178 public static RegisteredWaitHandle RegisterWaitForSingleObject( // throws RegisterWaitException
1179 WaitHandle waitObject,
1180 WaitOrTimerCallback callBack,
1182 int millisecondsTimeOutInterval,
1183 bool executeOnlyOnce // NOTE: we do not allow other options that allow the callback to be queued as an APC
1186 if (millisecondsTimeOutInterval < -1)
1187 throw new ArgumentOutOfRangeException(nameof(millisecondsTimeOutInterval), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
1188 StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
1189 return RegisterWaitForSingleObject(waitObject, callBack, state, (UInt32)millisecondsTimeOutInterval, executeOnlyOnce, ref stackMark, true);
1192 [System.Security.DynamicSecurityMethod] // Methods containing StackCrawlMark local var has to be marked DynamicSecurityMethod
1193 public static RegisteredWaitHandle UnsafeRegisterWaitForSingleObject( // throws RegisterWaitException
1194 WaitHandle waitObject,
1195 WaitOrTimerCallback callBack,
1197 int millisecondsTimeOutInterval,
1198 bool executeOnlyOnce // NOTE: we do not allow other options that allow the callback to be queued as an APC
1201 if (millisecondsTimeOutInterval < -1)
1202 throw new ArgumentOutOfRangeException(nameof(millisecondsTimeOutInterval), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
1203 StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
1204 return RegisterWaitForSingleObject(waitObject, callBack, state, (UInt32)millisecondsTimeOutInterval, executeOnlyOnce, ref stackMark, false);
1207 [System.Security.DynamicSecurityMethod] // Methods containing StackCrawlMark local var has to be marked DynamicSecurityMethod
1208 public static RegisteredWaitHandle RegisterWaitForSingleObject( // throws RegisterWaitException
1209 WaitHandle waitObject,
1210 WaitOrTimerCallback callBack,
1212 long millisecondsTimeOutInterval,
1213 bool executeOnlyOnce // NOTE: we do not allow other options that allow the callback to be queued as an APC
1216 if (millisecondsTimeOutInterval < -1)
1217 throw new ArgumentOutOfRangeException(nameof(millisecondsTimeOutInterval), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
1218 StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
1219 return RegisterWaitForSingleObject(waitObject, callBack, state, (UInt32)millisecondsTimeOutInterval, executeOnlyOnce, ref stackMark, true);
1222 [System.Security.DynamicSecurityMethod] // Methods containing StackCrawlMark local var has to be marked DynamicSecurityMethod
1223 public static RegisteredWaitHandle UnsafeRegisterWaitForSingleObject( // throws RegisterWaitException
1224 WaitHandle waitObject,
1225 WaitOrTimerCallback callBack,
1227 long millisecondsTimeOutInterval,
1228 bool executeOnlyOnce // NOTE: we do not allow other options that allow the callback to be queued as an APC
1231 if (millisecondsTimeOutInterval < -1)
1232 throw new ArgumentOutOfRangeException(nameof(millisecondsTimeOutInterval), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
1233 StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
1234 return RegisterWaitForSingleObject(waitObject, callBack, state, (UInt32)millisecondsTimeOutInterval, executeOnlyOnce, ref stackMark, false);
1237 [System.Security.DynamicSecurityMethod] // Methods containing StackCrawlMark local var has to be marked DynamicSecurityMethod
1238 public static RegisteredWaitHandle RegisterWaitForSingleObject(
1239 WaitHandle waitObject,
1240 WaitOrTimerCallback callBack,
1243 bool executeOnlyOnce
1246 long tm = (long)timeout.TotalMilliseconds;
1248 throw new ArgumentOutOfRangeException(nameof(timeout), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
1249 if (tm > (long)Int32.MaxValue)
1250 throw new ArgumentOutOfRangeException(nameof(timeout), SR.ArgumentOutOfRange_LessEqualToIntegerMaxVal);
1251 StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
1252 return RegisterWaitForSingleObject(waitObject, callBack, state, (UInt32)tm, executeOnlyOnce, ref stackMark, true);
1255 [System.Security.DynamicSecurityMethod] // Methods containing StackCrawlMark local var has to be marked DynamicSecurityMethod
1256 public static RegisteredWaitHandle UnsafeRegisterWaitForSingleObject(
1257 WaitHandle waitObject,
1258 WaitOrTimerCallback callBack,
1261 bool executeOnlyOnce
1264 long tm = (long)timeout.TotalMilliseconds;
1266 throw new ArgumentOutOfRangeException(nameof(timeout), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
1267 if (tm > (long)Int32.MaxValue)
1268 throw new ArgumentOutOfRangeException(nameof(timeout), SR.ArgumentOutOfRange_LessEqualToIntegerMaxVal);
1269 StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
1270 return RegisterWaitForSingleObject(waitObject, callBack, state, (UInt32)tm, executeOnlyOnce, ref stackMark, false);
1273 public static bool QueueUserWorkItem(WaitCallback callBack) =>
1274 QueueUserWorkItem(callBack, null, preferLocal: false);
1276 public static bool QueueUserWorkItem(WaitCallback callBack, object state) =>
1277 QueueUserWorkItem(callBack, state, preferLocal: false);
1279 public static bool QueueUserWorkItem(WaitCallback callBack, object state, bool preferLocal)
1281 if (callBack == null)
1283 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.callBack);
1286 EnsureVMInitialized();
1288 ExecutionContext context = ExecutionContext.Capture();
1290 IThreadPoolWorkItem tpcallBack = (context != null && context.IsDefault) ?
1291 new QueueUserWorkItemCallbackDefaultContext(callBack, state) :
1292 (IThreadPoolWorkItem)new QueueUserWorkItemCallback(callBack, state, context);
1294 ThreadPoolGlobals.workQueue.Enqueue(tpcallBack, forceGlobal: !preferLocal);
1299 public static bool UnsafeQueueUserWorkItem(WaitCallback callBack, Object state)
1301 if (callBack == null)
1303 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.callBack);
1306 EnsureVMInitialized();
1308 IThreadPoolWorkItem tpcallBack = new QueueUserWorkItemCallback(callBack, state, null);
1310 ThreadPoolGlobals.workQueue.Enqueue(tpcallBack, forceGlobal: true);
1315 internal static void UnsafeQueueCustomWorkItem(IThreadPoolWorkItem workItem, bool forceGlobal)
1317 Debug.Assert(null != workItem);
1318 EnsureVMInitialized();
1319 ThreadPoolGlobals.workQueue.Enqueue(workItem, forceGlobal);
1322 // This method tries to take the target callback out of the current thread's queue.
1323 internal static bool TryPopCustomWorkItem(IThreadPoolWorkItem workItem)
1325 Debug.Assert(null != workItem);
1327 ThreadPoolGlobals.vmTpInitialized && // if not initialized, so there's no way this workitem was ever queued.
1328 ThreadPoolGlobals.workQueue.LocalFindAndPop(workItem);
1331 // Get all workitems. Called by TaskScheduler in its debugger hooks.
1332 internal static IEnumerable<IThreadPoolWorkItem> GetQueuedWorkItems()
1334 // Enumerate global queue
1335 foreach (IThreadPoolWorkItem workItem in ThreadPoolGlobals.workQueue.workItems)
1337 yield return workItem;
1340 // Enumerate each local queue
1341 foreach (ThreadPoolWorkQueue.WorkStealingQueue wsq in ThreadPoolWorkQueue.WorkStealingQueueList.Queues)
1343 if (wsq != null && wsq.m_array != null)
1345 IThreadPoolWorkItem[] items = wsq.m_array;
1346 for (int i = 0; i < items.Length; i++)
1348 IThreadPoolWorkItem item = items[i];
1358 internal static IEnumerable<IThreadPoolWorkItem> GetLocallyQueuedWorkItems()
1360 ThreadPoolWorkQueue.WorkStealingQueue wsq = ThreadPoolWorkQueueThreadLocals.threadLocals.workStealingQueue;
1361 if (wsq != null && wsq.m_array != null)
1363 IThreadPoolWorkItem[] items = wsq.m_array;
1364 for (int i = 0; i < items.Length; i++)
1366 IThreadPoolWorkItem item = items[i];
1373 internal static IEnumerable<IThreadPoolWorkItem> GetGloballyQueuedWorkItems() => ThreadPoolGlobals.workQueue.workItems;
1375 private static object[] ToObjectArray(IEnumerable<IThreadPoolWorkItem> workitems)
1378 foreach (IThreadPoolWorkItem item in workitems)
1383 object[] result = new object[i];
1385 foreach (IThreadPoolWorkItem item in workitems)
1387 if (i < result.Length) //just in case someone calls us while the queues are in motion
1395 // This is the method the debugger will actually call, if it ends up calling
1396 // into ThreadPool directly. Tests can use this to simulate a debugger, as well.
1397 internal static object[] GetQueuedWorkItemsForDebugger() =>
1398 ToObjectArray(GetQueuedWorkItems());
1400 internal static object[] GetGloballyQueuedWorkItemsForDebugger() =>
1401 ToObjectArray(GetGloballyQueuedWorkItems());
1403 internal static object[] GetLocallyQueuedWorkItemsForDebugger() =>
1404 ToObjectArray(GetLocallyQueuedWorkItems());
1406 [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
1407 internal static extern bool RequestWorkerThread();
1409 [MethodImplAttribute(MethodImplOptions.InternalCall)]
1410 unsafe private static extern bool PostQueuedCompletionStatus(NativeOverlapped* overlapped);
1412 [CLSCompliant(false)]
1413 unsafe public static bool UnsafeQueueNativeOverlapped(NativeOverlapped* overlapped) =>
1414 PostQueuedCompletionStatus(overlapped);
1416 // The thread pool maintains a per-appdomain managed work queue.
1417 // New thread pool entries are added in the managed queue.
1418 // The VM is responsible for the actual growing/shrinking of
1420 private static void EnsureVMInitialized()
1422 if (!ThreadPoolGlobals.vmTpInitialized)
1424 EnsureVMInitializedCore(); // separate out to help with inlining
1428 private static void EnsureVMInitializedCore()
1430 ThreadPool.InitializeVMTp(ref ThreadPoolGlobals.enableWorkerTracking);
1431 ThreadPoolGlobals.vmTpInitialized = true;
1436 [MethodImplAttribute(MethodImplOptions.InternalCall)]
1437 private static extern bool SetMinThreadsNative(int workerThreads, int completionPortThreads);
1439 [MethodImplAttribute(MethodImplOptions.InternalCall)]
1440 private static extern bool SetMaxThreadsNative(int workerThreads, int completionPortThreads);
1442 [MethodImplAttribute(MethodImplOptions.InternalCall)]
1443 private static extern void GetMinThreadsNative(out int workerThreads, out int completionPortThreads);
1445 [MethodImplAttribute(MethodImplOptions.InternalCall)]
1446 private static extern void GetMaxThreadsNative(out int workerThreads, out int completionPortThreads);
1448 [MethodImplAttribute(MethodImplOptions.InternalCall)]
1449 private static extern void GetAvailableThreadsNative(out int workerThreads, out int completionPortThreads);
1451 [MethodImplAttribute(MethodImplOptions.InternalCall)]
1452 internal static extern bool NotifyWorkItemComplete();
1454 [MethodImplAttribute(MethodImplOptions.InternalCall)]
1455 internal static extern void ReportThreadStatus(bool isWorking);
1457 internal static void NotifyWorkItemProgress()
1459 if (!ThreadPoolGlobals.vmTpInitialized)
1460 ThreadPool.InitializeVMTp(ref ThreadPoolGlobals.enableWorkerTracking);
1461 NotifyWorkItemProgressNative();
1464 [MethodImplAttribute(MethodImplOptions.InternalCall)]
1465 internal static extern void NotifyWorkItemProgressNative();
1467 [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
1468 private static extern void InitializeVMTp(ref bool enableWorkerTracking);
1470 [MethodImplAttribute(MethodImplOptions.InternalCall)]
1471 private static extern IntPtr RegisterWaitForSingleObjectNative(
1472 WaitHandle waitHandle,
1474 uint timeOutInterval,
1475 bool executeOnlyOnce,
1476 RegisteredWaitHandle registeredWaitHandle,
1477 ref StackCrawlMark stackMark,
1482 [Obsolete("ThreadPool.BindHandle(IntPtr) has been deprecated. Please use ThreadPool.BindHandle(SafeHandle) instead.", false)]
1483 public static bool BindHandle(IntPtr osHandle)
1485 return BindIOCompletionCallbackNative(osHandle);
1488 public static bool BindHandle(SafeHandle osHandle)
1490 if (osHandle == null)
1491 throw new ArgumentNullException(nameof(osHandle));
1494 bool mustReleaseSafeHandle = false;
1495 RuntimeHelpers.PrepareConstrainedRegions();
1498 osHandle.DangerousAddRef(ref mustReleaseSafeHandle);
1499 ret = BindIOCompletionCallbackNative(osHandle.DangerousGetHandle());
1503 if (mustReleaseSafeHandle)
1504 osHandle.DangerousRelease();
1509 [MethodImplAttribute(MethodImplOptions.InternalCall)]
1510 private static extern bool BindIOCompletionCallbackNative(IntPtr fileHandle);