ThreadPool.cs 52 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298
  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.
  4. /*=============================================================================
  5. **
  6. **
  7. **
  8. ** Purpose: Class for creating and managing a threadpool
  9. **
  10. **
  11. =============================================================================*/
  12. using System.Collections.Concurrent;
  13. using System.Collections.Generic;
  14. using System.Diagnostics;
  15. using System.Diagnostics.CodeAnalysis;
  16. using System.Diagnostics.Tracing;
  17. using System.Runtime.CompilerServices;
  18. using System.Runtime.InteropServices;
  19. using System.Threading.Tasks;
  20. using Internal.Runtime.CompilerServices;
  21. namespace System.Threading
  22. {
  23. internal static class ThreadPoolGlobals
  24. {
  25. public static readonly int processorCount = Environment.ProcessorCount;
  26. public static volatile bool threadPoolInitialized;
  27. public static bool enableWorkerTracking;
  28. public static readonly ThreadPoolWorkQueue workQueue = new ThreadPoolWorkQueue();
  29. /// <summary>Shim used to invoke <see cref="IAsyncStateMachineBox.MoveNext"/> of the supplied <see cref="IAsyncStateMachineBox"/>.</summary>
  30. internal static readonly Action<object?> s_invokeAsyncStateMachineBox = state =>
  31. {
  32. if (!(state is IAsyncStateMachineBox box))
  33. {
  34. ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.state);
  35. return;
  36. }
  37. box.MoveNext();
  38. };
  39. }
  40. [StructLayout(LayoutKind.Sequential)] // enforce layout so that padding reduces false sharing
  41. internal sealed class ThreadPoolWorkQueue
  42. {
  43. internal static class WorkStealingQueueList
  44. {
  45. private static volatile WorkStealingQueue[] _queues = new WorkStealingQueue[0];
  46. public static WorkStealingQueue[] Queues => _queues;
  47. public static void Add(WorkStealingQueue queue)
  48. {
  49. Debug.Assert(queue != null);
  50. while (true)
  51. {
  52. WorkStealingQueue[] oldQueues = _queues;
  53. Debug.Assert(Array.IndexOf(oldQueues, queue) == -1);
  54. var newQueues = new WorkStealingQueue[oldQueues.Length + 1];
  55. Array.Copy(oldQueues, 0, newQueues, 0, oldQueues.Length);
  56. newQueues[newQueues.Length - 1] = queue;
  57. if (Interlocked.CompareExchange(ref _queues, newQueues, oldQueues) == oldQueues)
  58. {
  59. break;
  60. }
  61. }
  62. }
  63. public static void Remove(WorkStealingQueue queue)
  64. {
  65. Debug.Assert(queue != null);
  66. while (true)
  67. {
  68. WorkStealingQueue[] oldQueues = _queues;
  69. if (oldQueues.Length == 0)
  70. {
  71. return;
  72. }
  73. int pos = Array.IndexOf(oldQueues, queue);
  74. if (pos == -1)
  75. {
  76. Debug.Fail("Should have found the queue");
  77. return;
  78. }
  79. var newQueues = new WorkStealingQueue[oldQueues.Length - 1];
  80. if (pos == 0)
  81. {
  82. Array.Copy(oldQueues, 1, newQueues, 0, newQueues.Length);
  83. }
  84. else if (pos == oldQueues.Length - 1)
  85. {
  86. Array.Copy(oldQueues, 0, newQueues, 0, newQueues.Length);
  87. }
  88. else
  89. {
  90. Array.Copy(oldQueues, 0, newQueues, 0, pos);
  91. Array.Copy(oldQueues, pos + 1, newQueues, pos, newQueues.Length - pos);
  92. }
  93. if (Interlocked.CompareExchange(ref _queues, newQueues, oldQueues) == oldQueues)
  94. {
  95. break;
  96. }
  97. }
  98. }
  99. }
  100. internal sealed class WorkStealingQueue
  101. {
  102. private const int INITIAL_SIZE = 32;
  103. internal volatile object?[] m_array = new object[INITIAL_SIZE]; // SOS's ThreadPool command depends on this name
  104. private volatile int m_mask = INITIAL_SIZE - 1;
  105. #if DEBUG
  106. // in debug builds, start at the end so we exercise the index reset logic.
  107. private const int START_INDEX = int.MaxValue;
  108. #else
  109. private const int START_INDEX = 0;
  110. #endif
  111. private volatile int m_headIndex = START_INDEX;
  112. private volatile int m_tailIndex = START_INDEX;
  113. private SpinLock m_foreignLock = new SpinLock(enableThreadOwnerTracking: false);
  114. public void LocalPush(object obj)
  115. {
  116. int tail = m_tailIndex;
  117. // We're going to increment the tail; if we'll overflow, then we need to reset our counts
  118. if (tail == int.MaxValue)
  119. {
  120. bool lockTaken = false;
  121. try
  122. {
  123. m_foreignLock.Enter(ref lockTaken);
  124. if (m_tailIndex == int.MaxValue)
  125. {
  126. //
  127. // Rather than resetting to zero, we'll just mask off the bits we don't care about.
  128. // This way we don't need to rearrange the items already in the queue; they'll be found
  129. // correctly exactly where they are. One subtlety here is that we need to make sure that
  130. // if head is currently < tail, it remains that way. This happens to just fall out from
  131. // the bit-masking, because we only do this if tail == int.MaxValue, meaning that all
  132. // bits are set, so all of the bits we're keeping will also be set. Thus it's impossible
  133. // for the head to end up > than the tail, since you can't set any more bits than all of
  134. // them.
  135. //
  136. m_headIndex = m_headIndex & m_mask;
  137. m_tailIndex = tail = m_tailIndex & m_mask;
  138. Debug.Assert(m_headIndex <= m_tailIndex);
  139. }
  140. }
  141. finally
  142. {
  143. if (lockTaken)
  144. m_foreignLock.Exit(useMemoryBarrier: true);
  145. }
  146. }
  147. // When there are at least 2 elements' worth of space, we can take the fast path.
  148. if (tail < m_headIndex + m_mask)
  149. {
  150. Volatile.Write(ref m_array[tail & m_mask], obj);
  151. m_tailIndex = tail + 1;
  152. }
  153. else
  154. {
  155. // We need to contend with foreign pops, so we lock.
  156. bool lockTaken = false;
  157. try
  158. {
  159. m_foreignLock.Enter(ref lockTaken);
  160. int head = m_headIndex;
  161. int count = m_tailIndex - m_headIndex;
  162. // If there is still space (one left), just add the element.
  163. if (count >= m_mask)
  164. {
  165. // We're full; expand the queue by doubling its size.
  166. var newArray = new object?[m_array.Length << 1];
  167. for (int i = 0; i < m_array.Length; i++)
  168. newArray[i] = m_array[(i + head) & m_mask];
  169. // Reset the field values, incl. the mask.
  170. m_array = newArray;
  171. m_headIndex = 0;
  172. m_tailIndex = tail = count;
  173. m_mask = (m_mask << 1) | 1;
  174. }
  175. Volatile.Write(ref m_array[tail & m_mask], obj);
  176. m_tailIndex = tail + 1;
  177. }
  178. finally
  179. {
  180. if (lockTaken)
  181. m_foreignLock.Exit(useMemoryBarrier: false);
  182. }
  183. }
  184. }
  185. [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "Reviewed for thread safety")]
  186. public bool LocalFindAndPop(object obj)
  187. {
  188. // Fast path: check the tail. If equal, we can skip the lock.
  189. if (m_array[(m_tailIndex - 1) & m_mask] == obj)
  190. {
  191. object? unused = LocalPop();
  192. Debug.Assert(unused == null || unused == obj);
  193. return unused != null;
  194. }
  195. // Else, do an O(N) search for the work item. The theory of work stealing and our
  196. // inlining logic is that most waits will happen on recently queued work. And
  197. // since recently queued work will be close to the tail end (which is where we
  198. // begin our search), we will likely find it quickly. In the worst case, we
  199. // will traverse the whole local queue; this is typically not going to be a
  200. // problem (although degenerate cases are clearly an issue) because local work
  201. // queues tend to be somewhat shallow in length, and because if we fail to find
  202. // the work item, we are about to block anyway (which is very expensive).
  203. for (int i = m_tailIndex - 2; i >= m_headIndex; i--)
  204. {
  205. if (m_array[i & m_mask] == obj)
  206. {
  207. // If we found the element, block out steals to avoid interference.
  208. bool lockTaken = false;
  209. try
  210. {
  211. m_foreignLock.Enter(ref lockTaken);
  212. // If we encountered a race condition, bail.
  213. if (m_array[i & m_mask] == null)
  214. return false;
  215. // Otherwise, null out the element.
  216. Volatile.Write(ref m_array[i & m_mask], null);
  217. // And then check to see if we can fix up the indexes (if we're at
  218. // the edge). If we can't, we just leave nulls in the array and they'll
  219. // get filtered out eventually (but may lead to superfluous resizing).
  220. if (i == m_tailIndex)
  221. m_tailIndex -= 1;
  222. else if (i == m_headIndex)
  223. m_headIndex += 1;
  224. return true;
  225. }
  226. finally
  227. {
  228. if (lockTaken)
  229. m_foreignLock.Exit(useMemoryBarrier: false);
  230. }
  231. }
  232. }
  233. return false;
  234. }
  235. public object? LocalPop() => m_headIndex < m_tailIndex ? LocalPopCore() : null;
  236. [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "Reviewed for thread safety")]
  237. private object? LocalPopCore()
  238. {
  239. while (true)
  240. {
  241. int tail = m_tailIndex;
  242. if (m_headIndex >= tail)
  243. {
  244. return null;
  245. }
  246. // Decrement the tail using a fence to ensure subsequent read doesn't come before.
  247. tail -= 1;
  248. Interlocked.Exchange(ref m_tailIndex, tail);
  249. // If there is no interaction with a take, we can head down the fast path.
  250. if (m_headIndex <= tail)
  251. {
  252. int idx = tail & m_mask;
  253. object? obj = Volatile.Read(ref m_array[idx]);
  254. // Check for nulls in the array.
  255. if (obj == null) continue;
  256. m_array[idx] = null;
  257. return obj;
  258. }
  259. else
  260. {
  261. // Interaction with takes: 0 or 1 elements left.
  262. bool lockTaken = false;
  263. try
  264. {
  265. m_foreignLock.Enter(ref lockTaken);
  266. if (m_headIndex <= tail)
  267. {
  268. // Element still available. Take it.
  269. int idx = tail & m_mask;
  270. object? obj = Volatile.Read(ref m_array[idx]);
  271. // Check for nulls in the array.
  272. if (obj == null) continue;
  273. m_array[idx] = null;
  274. return obj;
  275. }
  276. else
  277. {
  278. // If we encountered a race condition and element was stolen, restore the tail.
  279. m_tailIndex = tail + 1;
  280. return null;
  281. }
  282. }
  283. finally
  284. {
  285. if (lockTaken)
  286. m_foreignLock.Exit(useMemoryBarrier: false);
  287. }
  288. }
  289. }
  290. }
  291. public bool CanSteal => m_headIndex < m_tailIndex;
  292. public object? TrySteal(ref bool missedSteal)
  293. {
  294. while (true)
  295. {
  296. if (CanSteal)
  297. {
  298. bool taken = false;
  299. try
  300. {
  301. m_foreignLock.TryEnter(ref taken);
  302. if (taken)
  303. {
  304. // Increment head, and ensure read of tail doesn't move before it (fence).
  305. int head = m_headIndex;
  306. Interlocked.Exchange(ref m_headIndex, head + 1);
  307. if (head < m_tailIndex)
  308. {
  309. int idx = head & m_mask;
  310. object? obj = Volatile.Read(ref m_array[idx]);
  311. // Check for nulls in the array.
  312. if (obj == null) continue;
  313. m_array[idx] = null;
  314. return obj;
  315. }
  316. else
  317. {
  318. // Failed, restore head.
  319. m_headIndex = head;
  320. }
  321. }
  322. }
  323. finally
  324. {
  325. if (taken)
  326. m_foreignLock.Exit(useMemoryBarrier: false);
  327. }
  328. missedSteal = true;
  329. }
  330. return null;
  331. }
  332. }
  333. public int Count
  334. {
  335. get
  336. {
  337. bool lockTaken = false;
  338. try
  339. {
  340. m_foreignLock.Enter(ref lockTaken);
  341. return Math.Max(0, m_tailIndex - m_headIndex);
  342. }
  343. finally
  344. {
  345. if (lockTaken)
  346. {
  347. m_foreignLock.Exit(useMemoryBarrier: false);
  348. }
  349. }
  350. }
  351. }
  352. }
  353. internal bool loggingEnabled;
  354. internal readonly ConcurrentQueue<object> workItems = new ConcurrentQueue<object>(); // SOS's ThreadPool command depends on this name
  355. private Internal.PaddingFor32 pad1;
  356. private volatile int numOutstandingThreadRequests = 0;
  357. private Internal.PaddingFor32 pad2;
  358. public ThreadPoolWorkQueue()
  359. {
  360. loggingEnabled = FrameworkEventSource.Log.IsEnabled(EventLevel.Verbose, FrameworkEventSource.Keywords.ThreadPool | FrameworkEventSource.Keywords.ThreadTransfer);
  361. }
  362. public ThreadPoolWorkQueueThreadLocals GetOrCreateThreadLocals() =>
  363. ThreadPoolWorkQueueThreadLocals.threadLocals ?? CreateThreadLocals();
  364. [MethodImpl(MethodImplOptions.NoInlining)]
  365. private ThreadPoolWorkQueueThreadLocals CreateThreadLocals()
  366. {
  367. Debug.Assert(ThreadPoolWorkQueueThreadLocals.threadLocals == null);
  368. return (ThreadPoolWorkQueueThreadLocals.threadLocals = new ThreadPoolWorkQueueThreadLocals(this));
  369. }
  370. internal void EnsureThreadRequested()
  371. {
  372. //
  373. // If we have not yet requested #procs threads, then request a new thread.
  374. //
  375. // CoreCLR: Note that there is a separate count in the VM which has already been incremented
  376. // by the VM by the time we reach this point.
  377. //
  378. int count = numOutstandingThreadRequests;
  379. while (count < ThreadPoolGlobals.processorCount)
  380. {
  381. int prev = Interlocked.CompareExchange(ref numOutstandingThreadRequests, count + 1, count);
  382. if (prev == count)
  383. {
  384. ThreadPool.RequestWorkerThread();
  385. break;
  386. }
  387. count = prev;
  388. }
  389. }
  390. internal void MarkThreadRequestSatisfied()
  391. {
  392. //
  393. // One of our outstanding thread requests has been satisfied.
  394. // Decrement the count so that future calls to EnsureThreadRequested will succeed.
  395. //
  396. // CoreCLR: Note that there is a separate count in the VM which has already been decremented
  397. // by the VM by the time we reach this point.
  398. //
  399. int count = numOutstandingThreadRequests;
  400. while (count > 0)
  401. {
  402. int prev = Interlocked.CompareExchange(ref numOutstandingThreadRequests, count - 1, count);
  403. if (prev == count)
  404. {
  405. break;
  406. }
  407. count = prev;
  408. }
  409. }
  410. public void Enqueue(object callback, bool forceGlobal)
  411. {
  412. Debug.Assert((callback is IThreadPoolWorkItem) ^ (callback is Task));
  413. if (loggingEnabled)
  414. System.Diagnostics.Tracing.FrameworkEventSource.Log.ThreadPoolEnqueueWorkObject(callback);
  415. ThreadPoolWorkQueueThreadLocals? tl = null;
  416. if (!forceGlobal)
  417. tl = ThreadPoolWorkQueueThreadLocals.threadLocals;
  418. if (null != tl)
  419. {
  420. tl.workStealingQueue.LocalPush(callback);
  421. }
  422. else
  423. {
  424. workItems.Enqueue(callback);
  425. }
  426. EnsureThreadRequested();
  427. }
  428. internal bool LocalFindAndPop(object callback)
  429. {
  430. ThreadPoolWorkQueueThreadLocals? tl = ThreadPoolWorkQueueThreadLocals.threadLocals;
  431. return tl != null && tl.workStealingQueue.LocalFindAndPop(callback);
  432. }
  433. public object? Dequeue(ThreadPoolWorkQueueThreadLocals tl, ref bool missedSteal)
  434. {
  435. WorkStealingQueue localWsq = tl.workStealingQueue;
  436. object? callback;
  437. if ((callback = localWsq.LocalPop()) == null && // first try the local queue
  438. !workItems.TryDequeue(out callback)) // then try the global queue
  439. {
  440. // finally try to steal from another thread's local queue
  441. WorkStealingQueue[] queues = WorkStealingQueueList.Queues;
  442. int c = queues.Length;
  443. Debug.Assert(c > 0, "There must at least be a queue for this thread.");
  444. int maxIndex = c - 1;
  445. int i = tl.random.Next(c);
  446. while (c > 0)
  447. {
  448. i = (i < maxIndex) ? i + 1 : 0;
  449. WorkStealingQueue otherQueue = queues[i];
  450. if (otherQueue != localWsq && otherQueue.CanSteal)
  451. {
  452. callback = otherQueue.TrySteal(ref missedSteal);
  453. if (callback != null)
  454. {
  455. break;
  456. }
  457. }
  458. c--;
  459. }
  460. }
  461. return callback;
  462. }
  463. public long LocalCount
  464. {
  465. get
  466. {
  467. long count = 0;
  468. foreach (WorkStealingQueue workStealingQueue in WorkStealingQueueList.Queues)
  469. {
  470. count += workStealingQueue.Count;
  471. }
  472. return count;
  473. }
  474. }
  475. public long GlobalCount => workItems.Count;
  476. /// <summary>
  477. /// Dispatches work items to this thread.
  478. /// </summary>
  479. /// <returns>
  480. /// <c>true</c> if this thread did as much work as was available or its quantum expired.
  481. /// <c>false</c> if this thread stopped working early.
  482. /// </returns>
  483. internal static bool Dispatch()
  484. {
  485. ThreadPoolWorkQueue outerWorkQueue = ThreadPoolGlobals.workQueue;
  486. //
  487. // Save the start time
  488. //
  489. int startTickCount = Environment.TickCount;
  490. //
  491. // Update our records to indicate that an outstanding request for a thread has now been fulfilled.
  492. // From this point on, we are responsible for requesting another thread if we stop working for any
  493. // reason, and we believe there might still be work in the queue.
  494. //
  495. // CoreCLR: Note that if this thread is aborted before we get a chance to request another one, the VM will
  496. // record a thread request on our behalf. So we don't need to worry about getting aborted right here.
  497. //
  498. outerWorkQueue.MarkThreadRequestSatisfied();
  499. // Has the desire for logging changed since the last time we entered?
  500. outerWorkQueue.loggingEnabled = FrameworkEventSource.Log.IsEnabled(EventLevel.Verbose, FrameworkEventSource.Keywords.ThreadPool | FrameworkEventSource.Keywords.ThreadTransfer);
  501. //
  502. // Assume that we're going to need another thread if this one returns to the VM. We'll set this to
  503. // false later, but only if we're absolutely certain that the queue is empty.
  504. //
  505. bool needAnotherThread = true;
  506. object? outerWorkItem = null;
  507. try
  508. {
  509. //
  510. // Set up our thread-local data
  511. //
  512. // Use operate on workQueue local to try block so it can be enregistered
  513. ThreadPoolWorkQueue workQueue = outerWorkQueue;
  514. ThreadPoolWorkQueueThreadLocals tl = workQueue.GetOrCreateThreadLocals();
  515. Thread currentThread = tl.currentThread;
  516. // Start on clean ExecutionContext and SynchronizationContext
  517. currentThread._executionContext = null;
  518. currentThread._synchronizationContext = null;
  519. //
  520. // Loop until our quantum expires or there is no work.
  521. //
  522. while (ThreadPool.KeepDispatching(startTickCount))
  523. {
  524. bool missedSteal = false;
  525. // Use operate on workItem local to try block so it can be enregistered
  526. object? workItem = outerWorkItem = workQueue.Dequeue(tl, ref missedSteal);
  527. if (workItem == null)
  528. {
  529. //
  530. // No work.
  531. // If we missed a steal, though, there may be more work in the queue.
  532. // Instead of looping around and trying again, we'll just request another thread. Hopefully the thread
  533. // that owns the contended work-stealing queue will pick up its own workitems in the meantime,
  534. // which will be more efficient than this thread doing it anyway.
  535. //
  536. needAnotherThread = missedSteal;
  537. // Tell the VM we're returning normally, not because Hill Climbing asked us to return.
  538. return true;
  539. }
  540. if (workQueue.loggingEnabled)
  541. System.Diagnostics.Tracing.FrameworkEventSource.Log.ThreadPoolDequeueWorkObject(workItem);
  542. //
  543. // If we found work, there may be more work. Ask for another thread so that the other work can be processed
  544. // in parallel. Note that this will only ask for a max of #procs threads, so it's safe to call it for every dequeue.
  545. //
  546. workQueue.EnsureThreadRequested();
  547. //
  548. // Execute the workitem outside of any finally blocks, so that it can be aborted if needed.
  549. //
  550. if (ThreadPoolGlobals.enableWorkerTracking)
  551. {
  552. bool reportedStatus = false;
  553. try
  554. {
  555. ThreadPool.ReportThreadStatus(isWorking: true);
  556. reportedStatus = true;
  557. if (workItem is Task task)
  558. {
  559. task.ExecuteFromThreadPool(currentThread);
  560. }
  561. else
  562. {
  563. Debug.Assert(workItem is IThreadPoolWorkItem);
  564. Unsafe.As<IThreadPoolWorkItem>(workItem).Execute();
  565. }
  566. }
  567. finally
  568. {
  569. if (reportedStatus)
  570. ThreadPool.ReportThreadStatus(isWorking: false);
  571. }
  572. }
  573. else if (workItem is Task task)
  574. {
  575. // Check for Task first as it's currently faster to type check
  576. // for Task and then Unsafe.As for the interface, rather than
  577. // vice versa, in particular when the object implements a bunch
  578. // of interfaces.
  579. task.ExecuteFromThreadPool(currentThread);
  580. }
  581. else
  582. {
  583. Debug.Assert(workItem is IThreadPoolWorkItem);
  584. Unsafe.As<IThreadPoolWorkItem>(workItem).Execute();
  585. }
  586. currentThread.ResetThreadPoolThread();
  587. // Release refs
  588. outerWorkItem = workItem = null;
  589. // Return to clean ExecutionContext and SynchronizationContext
  590. ExecutionContext.ResetThreadPoolThread(currentThread);
  591. //
  592. // Notify the VM that we executed this workitem. This is also our opportunity to ask whether Hill Climbing wants
  593. // us to return the thread to the pool or not.
  594. //
  595. if (!ThreadPool.NotifyWorkItemComplete())
  596. return false;
  597. }
  598. // If we get here, it's because our quantum expired. Tell the VM we're returning normally.
  599. return true;
  600. }
  601. finally
  602. {
  603. //
  604. // If we are exiting for any reason other than that the queue is definitely empty, ask for another
  605. // thread to pick up where we left off.
  606. //
  607. if (needAnotherThread)
  608. outerWorkQueue.EnsureThreadRequested();
  609. }
  610. }
  611. }
  612. // Simple random number generator. We don't need great randomness, we just need a little and for it to be fast.
  613. internal struct FastRandom // xorshift prng
  614. {
  615. private uint _w, _x, _y, _z;
  616. public FastRandom(int seed)
  617. {
  618. _x = (uint)seed;
  619. _w = 88675123;
  620. _y = 362436069;
  621. _z = 521288629;
  622. }
  623. public int Next(int maxValue)
  624. {
  625. Debug.Assert(maxValue > 0);
  626. uint t = _x ^ (_x << 11);
  627. _x = _y; _y = _z; _z = _w;
  628. _w = _w ^ (_w >> 19) ^ (t ^ (t >> 8));
  629. return (int)(_w % (uint)maxValue);
  630. }
  631. }
  632. // Holds a WorkStealingQueue, and removes it from the list when this object is no longer referenced.
  633. internal sealed class ThreadPoolWorkQueueThreadLocals
  634. {
  635. [ThreadStatic]
  636. public static ThreadPoolWorkQueueThreadLocals? threadLocals;
  637. public readonly ThreadPoolWorkQueue workQueue;
  638. public readonly ThreadPoolWorkQueue.WorkStealingQueue workStealingQueue;
  639. public readonly Thread currentThread;
  640. public FastRandom random = new FastRandom(Thread.CurrentThread.ManagedThreadId); // mutable struct, do not copy or make readonly
  641. public ThreadPoolWorkQueueThreadLocals(ThreadPoolWorkQueue tpq)
  642. {
  643. workQueue = tpq;
  644. workStealingQueue = new ThreadPoolWorkQueue.WorkStealingQueue();
  645. ThreadPoolWorkQueue.WorkStealingQueueList.Add(workStealingQueue);
  646. currentThread = Thread.CurrentThread;
  647. }
  648. ~ThreadPoolWorkQueueThreadLocals()
  649. {
  650. // Transfer any pending workitems into the global queue so that they will be executed by another thread
  651. if (null != workStealingQueue)
  652. {
  653. if (null != workQueue)
  654. {
  655. object? cb;
  656. while ((cb = workStealingQueue.LocalPop()) != null)
  657. {
  658. Debug.Assert(null != cb);
  659. workQueue.Enqueue(cb, forceGlobal: true);
  660. }
  661. }
  662. ThreadPoolWorkQueue.WorkStealingQueueList.Remove(workStealingQueue);
  663. }
  664. }
  665. }
  666. public delegate void WaitCallback(object? state);
  667. public delegate void WaitOrTimerCallback(object? state, bool timedOut); // signaled or timed out
  668. internal abstract class QueueUserWorkItemCallbackBase : IThreadPoolWorkItem
  669. {
  670. #if DEBUG
  671. private int executed;
  672. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1821:RemoveEmptyFinalizers")]
  673. ~QueueUserWorkItemCallbackBase()
  674. {
  675. Interlocked.MemoryBarrier(); // ensure that an old cached value is not read below
  676. Debug.Assert(
  677. executed != 0, "A QueueUserWorkItemCallback was never called!");
  678. }
  679. #endif
  680. public virtual void Execute()
  681. {
  682. #if DEBUG
  683. GC.SuppressFinalize(this);
  684. Debug.Assert(
  685. 0 == Interlocked.Exchange(ref executed, 1),
  686. "A QueueUserWorkItemCallback was called twice!");
  687. #endif
  688. }
  689. }
  690. internal sealed class QueueUserWorkItemCallback : QueueUserWorkItemCallbackBase
  691. {
  692. private WaitCallback? _callback; // SOS's ThreadPool command depends on this name
  693. private readonly object? _state;
  694. private readonly ExecutionContext _context;
  695. private static readonly Action<QueueUserWorkItemCallback> s_executionContextShim = quwi =>
  696. {
  697. Debug.Assert(quwi._callback != null);
  698. WaitCallback callback = quwi._callback;
  699. quwi._callback = null;
  700. callback(quwi._state);
  701. };
  702. internal QueueUserWorkItemCallback(WaitCallback callback, object? state, ExecutionContext context)
  703. {
  704. Debug.Assert(context != null);
  705. _callback = callback;
  706. _state = state;
  707. _context = context;
  708. }
  709. public override void Execute()
  710. {
  711. base.Execute();
  712. ExecutionContext.RunForThreadPoolUnsafe(_context, s_executionContextShim, this);
  713. }
  714. }
  715. internal sealed class QueueUserWorkItemCallback<TState> : QueueUserWorkItemCallbackBase
  716. {
  717. private Action<TState>? _callback; // SOS's ThreadPool command depends on this name
  718. private readonly TState _state;
  719. private readonly ExecutionContext _context;
  720. internal QueueUserWorkItemCallback(Action<TState> callback, TState state, ExecutionContext context)
  721. {
  722. Debug.Assert(callback != null);
  723. _callback = callback;
  724. _state = state;
  725. _context = context;
  726. }
  727. public override void Execute()
  728. {
  729. base.Execute();
  730. Debug.Assert(_callback != null);
  731. Action<TState> callback = _callback;
  732. _callback = null;
  733. ExecutionContext.RunForThreadPoolUnsafe(_context, callback, in _state);
  734. }
  735. }
  736. internal sealed class QueueUserWorkItemCallbackDefaultContext : QueueUserWorkItemCallbackBase
  737. {
  738. private WaitCallback? _callback; // SOS's ThreadPool command depends on this name
  739. private readonly object? _state;
  740. internal QueueUserWorkItemCallbackDefaultContext(WaitCallback callback, object? state)
  741. {
  742. Debug.Assert(callback != null);
  743. _callback = callback;
  744. _state = state;
  745. }
  746. public override void Execute()
  747. {
  748. ExecutionContext.CheckThreadPoolAndContextsAreDefault();
  749. base.Execute();
  750. Debug.Assert(_callback != null);
  751. WaitCallback callback = _callback;
  752. _callback = null;
  753. callback(_state);
  754. // ThreadPoolWorkQueue.Dispatch will handle notifications and reset EC and SyncCtx back to default
  755. }
  756. }
  757. internal sealed class QueueUserWorkItemCallbackDefaultContext<TState> : QueueUserWorkItemCallbackBase
  758. {
  759. private Action<TState>? _callback; // SOS's ThreadPool command depends on this name
  760. private readonly TState _state;
  761. internal QueueUserWorkItemCallbackDefaultContext(Action<TState> callback, TState state)
  762. {
  763. Debug.Assert(callback != null);
  764. _callback = callback;
  765. _state = state;
  766. }
  767. public override void Execute()
  768. {
  769. ExecutionContext.CheckThreadPoolAndContextsAreDefault();
  770. base.Execute();
  771. Debug.Assert(_callback != null);
  772. Action<TState> callback = _callback;
  773. _callback = null;
  774. callback(_state);
  775. // ThreadPoolWorkQueue.Dispatch will handle notifications and reset EC and SyncCtx back to default
  776. }
  777. }
  778. internal sealed class _ThreadPoolWaitOrTimerCallback
  779. {
  780. private WaitOrTimerCallback _waitOrTimerCallback;
  781. private ExecutionContext? _executionContext;
  782. private object? _state;
  783. private static readonly ContextCallback _ccbt = new ContextCallback(WaitOrTimerCallback_Context_t);
  784. private static readonly ContextCallback _ccbf = new ContextCallback(WaitOrTimerCallback_Context_f);
  785. internal _ThreadPoolWaitOrTimerCallback(WaitOrTimerCallback waitOrTimerCallback, object? state, bool flowExecutionContext)
  786. {
  787. _waitOrTimerCallback = waitOrTimerCallback;
  788. _state = state;
  789. if (flowExecutionContext)
  790. {
  791. // capture the exection context
  792. _executionContext = ExecutionContext.Capture();
  793. }
  794. }
  795. private static void WaitOrTimerCallback_Context_t(object? state) =>
  796. WaitOrTimerCallback_Context(state, timedOut: true);
  797. private static void WaitOrTimerCallback_Context_f(object? state) =>
  798. WaitOrTimerCallback_Context(state, timedOut: false);
  799. private static void WaitOrTimerCallback_Context(object? state, bool timedOut)
  800. {
  801. _ThreadPoolWaitOrTimerCallback helper = (_ThreadPoolWaitOrTimerCallback)state!;
  802. helper._waitOrTimerCallback(helper._state, timedOut);
  803. }
  804. // call back helper
  805. internal static void PerformWaitOrTimerCallback(_ThreadPoolWaitOrTimerCallback helper, bool timedOut)
  806. {
  807. Debug.Assert(helper != null, "Null state passed to PerformWaitOrTimerCallback!");
  808. // call directly if it is an unsafe call OR EC flow is suppressed
  809. ExecutionContext? context = helper._executionContext;
  810. if (context == null)
  811. {
  812. WaitOrTimerCallback callback = helper._waitOrTimerCallback;
  813. callback(helper._state, timedOut);
  814. }
  815. else
  816. {
  817. ExecutionContext.Run(context, timedOut ? _ccbt : _ccbf, helper);
  818. }
  819. }
  820. }
  821. public static partial class ThreadPool
  822. {
  823. [CLSCompliant(false)]
  824. public static RegisteredWaitHandle RegisterWaitForSingleObject(
  825. WaitHandle waitObject,
  826. WaitOrTimerCallback callBack,
  827. object? state,
  828. uint millisecondsTimeOutInterval,
  829. bool executeOnlyOnce // NOTE: we do not allow other options that allow the callback to be queued as an APC
  830. )
  831. {
  832. if (millisecondsTimeOutInterval > (uint)int.MaxValue && millisecondsTimeOutInterval != uint.MaxValue)
  833. throw new ArgumentOutOfRangeException(nameof(millisecondsTimeOutInterval), SR.ArgumentOutOfRange_LessEqualToIntegerMaxVal);
  834. return RegisterWaitForSingleObject(waitObject, callBack, state, millisecondsTimeOutInterval, executeOnlyOnce, true);
  835. }
  836. [CLSCompliant(false)]
  837. public static RegisteredWaitHandle UnsafeRegisterWaitForSingleObject(
  838. WaitHandle waitObject,
  839. WaitOrTimerCallback callBack,
  840. object? state,
  841. uint millisecondsTimeOutInterval,
  842. bool executeOnlyOnce // NOTE: we do not allow other options that allow the callback to be queued as an APC
  843. )
  844. {
  845. if (millisecondsTimeOutInterval > (uint)int.MaxValue && millisecondsTimeOutInterval != uint.MaxValue)
  846. throw new ArgumentOutOfRangeException(nameof(millisecondsTimeOutInterval), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
  847. return RegisterWaitForSingleObject(waitObject, callBack, state, millisecondsTimeOutInterval, executeOnlyOnce, false);
  848. }
  849. public static RegisteredWaitHandle RegisterWaitForSingleObject(
  850. WaitHandle waitObject,
  851. WaitOrTimerCallback callBack,
  852. object? state,
  853. int millisecondsTimeOutInterval,
  854. bool executeOnlyOnce // NOTE: we do not allow other options that allow the callback to be queued as an APC
  855. )
  856. {
  857. if (millisecondsTimeOutInterval < -1)
  858. throw new ArgumentOutOfRangeException(nameof(millisecondsTimeOutInterval), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
  859. return RegisterWaitForSingleObject(waitObject, callBack, state, (uint)millisecondsTimeOutInterval, executeOnlyOnce, true);
  860. }
  861. public static RegisteredWaitHandle UnsafeRegisterWaitForSingleObject(
  862. WaitHandle waitObject,
  863. WaitOrTimerCallback callBack,
  864. object? state,
  865. int millisecondsTimeOutInterval,
  866. bool executeOnlyOnce // NOTE: we do not allow other options that allow the callback to be queued as an APC
  867. )
  868. {
  869. if (millisecondsTimeOutInterval < -1)
  870. throw new ArgumentOutOfRangeException(nameof(millisecondsTimeOutInterval), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
  871. return RegisterWaitForSingleObject(waitObject, callBack, state, (uint)millisecondsTimeOutInterval, executeOnlyOnce, false);
  872. }
  873. public static RegisteredWaitHandle RegisterWaitForSingleObject(
  874. WaitHandle waitObject,
  875. WaitOrTimerCallback callBack,
  876. object? state,
  877. long millisecondsTimeOutInterval,
  878. bool executeOnlyOnce // NOTE: we do not allow other options that allow the callback to be queued as an APC
  879. )
  880. {
  881. if (millisecondsTimeOutInterval < -1)
  882. throw new ArgumentOutOfRangeException(nameof(millisecondsTimeOutInterval), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
  883. if (millisecondsTimeOutInterval > (uint)int.MaxValue)
  884. throw new ArgumentOutOfRangeException(nameof(millisecondsTimeOutInterval), SR.ArgumentOutOfRange_LessEqualToIntegerMaxVal);
  885. return RegisterWaitForSingleObject(waitObject, callBack, state, (uint)millisecondsTimeOutInterval, executeOnlyOnce, true);
  886. }
  887. public static RegisteredWaitHandle UnsafeRegisterWaitForSingleObject(
  888. WaitHandle waitObject,
  889. WaitOrTimerCallback callBack,
  890. object? state,
  891. long millisecondsTimeOutInterval,
  892. bool executeOnlyOnce // NOTE: we do not allow other options that allow the callback to be queued as an APC
  893. )
  894. {
  895. if (millisecondsTimeOutInterval < -1)
  896. throw new ArgumentOutOfRangeException(nameof(millisecondsTimeOutInterval), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
  897. if (millisecondsTimeOutInterval > (uint)int.MaxValue)
  898. throw new ArgumentOutOfRangeException(nameof(millisecondsTimeOutInterval), SR.ArgumentOutOfRange_LessEqualToIntegerMaxVal);
  899. return RegisterWaitForSingleObject(waitObject, callBack, state, (uint)millisecondsTimeOutInterval, executeOnlyOnce, false);
  900. }
  901. public static RegisteredWaitHandle RegisterWaitForSingleObject(
  902. WaitHandle waitObject,
  903. WaitOrTimerCallback callBack,
  904. object? state,
  905. TimeSpan timeout,
  906. bool executeOnlyOnce
  907. )
  908. {
  909. long tm = (long)timeout.TotalMilliseconds;
  910. if (tm < -1)
  911. throw new ArgumentOutOfRangeException(nameof(timeout), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
  912. if (tm > (long)int.MaxValue)
  913. throw new ArgumentOutOfRangeException(nameof(timeout), SR.ArgumentOutOfRange_LessEqualToIntegerMaxVal);
  914. return RegisterWaitForSingleObject(waitObject, callBack, state, (uint)tm, executeOnlyOnce, true);
  915. }
  916. public static RegisteredWaitHandle UnsafeRegisterWaitForSingleObject(
  917. WaitHandle waitObject,
  918. WaitOrTimerCallback callBack,
  919. object? state,
  920. TimeSpan timeout,
  921. bool executeOnlyOnce
  922. )
  923. {
  924. long tm = (long)timeout.TotalMilliseconds;
  925. if (tm < -1)
  926. throw new ArgumentOutOfRangeException(nameof(timeout), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
  927. if (tm > (long)int.MaxValue)
  928. throw new ArgumentOutOfRangeException(nameof(timeout), SR.ArgumentOutOfRange_LessEqualToIntegerMaxVal);
  929. return RegisterWaitForSingleObject(waitObject, callBack, state, (uint)tm, executeOnlyOnce, false);
  930. }
  931. public static bool QueueUserWorkItem(WaitCallback callBack) =>
  932. QueueUserWorkItem(callBack, null);
  933. public static bool QueueUserWorkItem(WaitCallback callBack, object? state)
  934. {
  935. if (callBack == null)
  936. {
  937. ThrowHelper.ThrowArgumentNullException(ExceptionArgument.callBack);
  938. }
  939. EnsureInitialized();
  940. ExecutionContext? context = ExecutionContext.Capture();
  941. object tpcallBack = (context == null || context.IsDefault) ?
  942. new QueueUserWorkItemCallbackDefaultContext(callBack!, state) :
  943. (object)new QueueUserWorkItemCallback(callBack!, state, context);
  944. ThreadPoolGlobals.workQueue.Enqueue(tpcallBack, forceGlobal: true);
  945. return true;
  946. }
  947. public static bool QueueUserWorkItem<TState>(Action<TState> callBack, TState state, bool preferLocal)
  948. {
  949. if (callBack == null)
  950. {
  951. ThrowHelper.ThrowArgumentNullException(ExceptionArgument.callBack);
  952. }
  953. EnsureInitialized();
  954. ExecutionContext? context = ExecutionContext.Capture();
  955. object tpcallBack = (context == null || context.IsDefault) ?
  956. new QueueUserWorkItemCallbackDefaultContext<TState>(callBack!, state) :
  957. (object)new QueueUserWorkItemCallback<TState>(callBack!, state, context);
  958. ThreadPoolGlobals.workQueue.Enqueue(tpcallBack, forceGlobal: !preferLocal);
  959. return true;
  960. }
  961. public static bool UnsafeQueueUserWorkItem<TState>(Action<TState> callBack, TState state, bool preferLocal)
  962. {
  963. if (callBack == null)
  964. {
  965. ThrowHelper.ThrowArgumentNullException(ExceptionArgument.callBack);
  966. }
  967. // If the callback is the runtime-provided invocation of an IAsyncStateMachineBox,
  968. // then we can queue the Task state directly to the ThreadPool instead of
  969. // wrapping it in a QueueUserWorkItemCallback.
  970. //
  971. // This occurs when user code queues its provided continuation to the ThreadPool;
  972. // internally we call UnsafeQueueUserWorkItemInternal directly for Tasks.
  973. if (ReferenceEquals(callBack, ThreadPoolGlobals.s_invokeAsyncStateMachineBox))
  974. {
  975. if (!(state is IAsyncStateMachineBox))
  976. {
  977. // The provided state must be the internal IAsyncStateMachineBox (Task) type
  978. ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.state);
  979. }
  980. UnsafeQueueUserWorkItemInternal((object)state!, preferLocal);
  981. return true;
  982. }
  983. EnsureInitialized();
  984. ThreadPoolGlobals.workQueue.Enqueue(
  985. new QueueUserWorkItemCallbackDefaultContext<TState>(callBack!, state), forceGlobal: !preferLocal);
  986. return true;
  987. }
  988. public static bool UnsafeQueueUserWorkItem(WaitCallback callBack, object? state)
  989. {
  990. if (callBack == null)
  991. {
  992. ThrowHelper.ThrowArgumentNullException(ExceptionArgument.callBack);
  993. }
  994. EnsureInitialized();
  995. object tpcallBack = new QueueUserWorkItemCallbackDefaultContext(callBack!, state);
  996. ThreadPoolGlobals.workQueue.Enqueue(tpcallBack, forceGlobal: true);
  997. return true;
  998. }
  999. public static bool UnsafeQueueUserWorkItem(IThreadPoolWorkItem callBack, bool preferLocal)
  1000. {
  1001. if (callBack == null)
  1002. {
  1003. ThrowHelper.ThrowArgumentNullException(ExceptionArgument.callBack);
  1004. }
  1005. if (callBack is Task)
  1006. {
  1007. // Prevent code from queueing a derived Task that also implements the interface,
  1008. // as that would bypass Task.Start and its safety checks.
  1009. ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.callBack);
  1010. }
  1011. UnsafeQueueUserWorkItemInternal(callBack!, preferLocal);
  1012. return true;
  1013. }
  1014. internal static void UnsafeQueueUserWorkItemInternal(object callBack, bool preferLocal)
  1015. {
  1016. Debug.Assert((callBack is IThreadPoolWorkItem) ^ (callBack is Task));
  1017. EnsureInitialized();
  1018. ThreadPoolGlobals.workQueue.Enqueue(callBack, forceGlobal: !preferLocal);
  1019. }
  1020. // This method tries to take the target callback out of the current thread's queue.
  1021. internal static bool TryPopCustomWorkItem(object workItem)
  1022. {
  1023. Debug.Assert(null != workItem);
  1024. return
  1025. ThreadPoolGlobals.threadPoolInitialized && // if not initialized, so there's no way this workitem was ever queued.
  1026. ThreadPoolGlobals.workQueue.LocalFindAndPop(workItem);
  1027. }
  1028. // Get all workitems. Called by TaskScheduler in its debugger hooks.
  1029. internal static IEnumerable<object> GetQueuedWorkItems()
  1030. {
  1031. // Enumerate global queue
  1032. foreach (object workItem in ThreadPoolGlobals.workQueue.workItems)
  1033. {
  1034. yield return workItem;
  1035. }
  1036. // Enumerate each local queue
  1037. foreach (ThreadPoolWorkQueue.WorkStealingQueue wsq in ThreadPoolWorkQueue.WorkStealingQueueList.Queues)
  1038. {
  1039. if (wsq != null && wsq.m_array != null)
  1040. {
  1041. object?[] items = wsq.m_array;
  1042. for (int i = 0; i < items.Length; i++)
  1043. {
  1044. object? item = items[i];
  1045. if (item != null)
  1046. {
  1047. yield return item;
  1048. }
  1049. }
  1050. }
  1051. }
  1052. }
  1053. internal static IEnumerable<object> GetLocallyQueuedWorkItems()
  1054. {
  1055. ThreadPoolWorkQueue.WorkStealingQueue? wsq = ThreadPoolWorkQueueThreadLocals.threadLocals?.workStealingQueue;
  1056. if (wsq != null && wsq.m_array != null)
  1057. {
  1058. object?[] items = wsq.m_array;
  1059. for (int i = 0; i < items.Length; i++)
  1060. {
  1061. object? item = items[i];
  1062. if (item != null)
  1063. yield return item;
  1064. }
  1065. }
  1066. }
  1067. internal static IEnumerable<object> GetGloballyQueuedWorkItems() => ThreadPoolGlobals.workQueue.workItems;
  1068. private static object[] ToObjectArray(IEnumerable<object> workitems)
  1069. {
  1070. int i = 0;
  1071. foreach (object item in workitems)
  1072. {
  1073. i++;
  1074. }
  1075. object[] result = new object[i];
  1076. i = 0;
  1077. foreach (object item in workitems)
  1078. {
  1079. if (i < result.Length) //just in case someone calls us while the queues are in motion
  1080. result[i] = item;
  1081. i++;
  1082. }
  1083. return result;
  1084. }
  1085. // This is the method the debugger will actually call, if it ends up calling
  1086. // into ThreadPool directly. Tests can use this to simulate a debugger, as well.
  1087. internal static object[] GetQueuedWorkItemsForDebugger() =>
  1088. ToObjectArray(GetQueuedWorkItems());
  1089. internal static object[] GetGloballyQueuedWorkItemsForDebugger() =>
  1090. ToObjectArray(GetGloballyQueuedWorkItems());
  1091. internal static object[] GetLocallyQueuedWorkItemsForDebugger() =>
  1092. ToObjectArray(GetLocallyQueuedWorkItems());
  1093. /// <summary>
  1094. /// Gets the number of work items that are currently queued to be processed.
  1095. /// </summary>
  1096. /// <remarks>
  1097. /// For a thread pool implementation that may have different types of work items, the count includes all types that can
  1098. /// be tracked, which may only be the user work items including tasks. Some implementations may also include queued
  1099. /// timer and wait callbacks in the count. On Windows, the count is unlikely to include the number of pending IO
  1100. /// completions, as they get posted directly to an IO completion port.
  1101. /// </remarks>
  1102. public static long PendingWorkItemCount
  1103. {
  1104. get
  1105. {
  1106. ThreadPoolWorkQueue workQueue = ThreadPoolGlobals.workQueue;
  1107. return workQueue.LocalCount + workQueue.GlobalCount + PendingUnmanagedWorkItemCount;
  1108. }
  1109. }
  1110. }
  1111. }