@@ -381,6 +381,17 @@ public int Count
381
381
}
382
382
}
383
383
384
+ #if CORECLR
385
+ // This config var can be used to enable an experimental mode that may reduce the effects of some priority inversion
386
+ // issues seen in cases involving a lot of sync-over-async. See EnqueueForPrioritizationExperiment() for more
387
+ // information. The mode is experimental and may change in the future.
388
+ internal static readonly bool s_prioritizationExperiment =
389
+ AppContextConfigHelper . GetBooleanConfig (
390
+ "System.Threading.ThreadPool.PrioritizationExperiment" ,
391
+ "DOTNET_ThreadPool_PrioritizationExperiment" ,
392
+ defaultValue : false ) ;
393
+ #endif
394
+
384
395
private const int ProcessorsPerAssignableWorkItemQueue = 16 ;
385
396
private static readonly int s_assignableWorkItemQueueCount =
386
397
Environment . ProcessorCount <= 32 ? 0 :
@@ -394,6 +405,11 @@ public int Count
394
405
internal readonly ConcurrentQueue < object > workItems = new ConcurrentQueue < object > ( ) ;
395
406
internal readonly ConcurrentQueue < object > highPriorityWorkItems = new ConcurrentQueue < object > ( ) ;
396
407
408
+ #if CORECLR
409
+ internal readonly ConcurrentQueue < object > lowPriorityWorkItems =
410
+ s_prioritizationExperiment ? new ConcurrentQueue < object > ( ) : null ! ;
411
+ #endif
412
+
397
413
// SOS's ThreadPool command depends on the following name. The global queue doesn't scale well beyond a point of
398
414
// concurrency. Some additional queues may be added and assigned to a limited number of worker threads if necessary to
399
415
// help with limiting the concurrency level.
@@ -598,23 +614,68 @@ public void Enqueue(object callback, bool forceGlobal)
598
614
if ( _loggingEnabled && FrameworkEventSource . Log . IsEnabled ( ) )
599
615
FrameworkEventSource . Log . ThreadPoolEnqueueWorkObject ( callback ) ;
600
616
601
- ThreadPoolWorkQueueThreadLocals ? tl ;
602
- if ( ! forceGlobal && ( tl = ThreadPoolWorkQueueThreadLocals . threadLocals ) != null )
617
+ #if CORECLR
618
+ if ( s_prioritizationExperiment )
603
619
{
604
- tl . workStealingQueue . LocalPush ( callback ) ;
620
+ EnqueueForPrioritizationExperiment ( callback , forceGlobal ) ;
605
621
}
606
622
else
623
+ #endif
607
624
{
608
- ConcurrentQueue < object > queue =
609
- s_assignableWorkItemQueueCount > 0 && ( tl = ThreadPoolWorkQueueThreadLocals . threadLocals ) != null
610
- ? tl . assignedGlobalWorkItemQueue
611
- : workItems ;
612
- queue . Enqueue ( callback ) ;
625
+ ThreadPoolWorkQueueThreadLocals ? tl ;
626
+ if ( ! forceGlobal && ( tl = ThreadPoolWorkQueueThreadLocals . threadLocals ) != null )
627
+ {
628
+ tl . workStealingQueue . LocalPush ( callback ) ;
629
+ }
630
+ else
631
+ {
632
+ ConcurrentQueue < object > queue =
633
+ s_assignableWorkItemQueueCount > 0 && ( tl = ThreadPoolWorkQueueThreadLocals . threadLocals ) != null
634
+ ? tl . assignedGlobalWorkItemQueue
635
+ : workItems ;
636
+ queue . Enqueue ( callback ) ;
637
+ }
613
638
}
614
639
615
640
EnsureThreadRequested ( ) ;
616
641
}
617
642
643
+ #if CORECLR
644
+ [ MethodImpl ( MethodImplOptions . NoInlining ) ]
645
+ private void EnqueueForPrioritizationExperiment ( object callback , bool forceGlobal )
646
+ {
647
+ ThreadPoolWorkQueueThreadLocals ? tl = ThreadPoolWorkQueueThreadLocals . threadLocals ;
648
+ if ( ! forceGlobal && tl != null )
649
+ {
650
+ tl . workStealingQueue . LocalPush ( callback ) ;
651
+ return ;
652
+ }
653
+
654
+ ConcurrentQueue < object > queue ;
655
+
656
+ // This is a rough and experimental attempt at identifying work items that should be lower priority than other
657
+ // global work items (even ones that haven't been queued yet), and to queue them to a low-priority global queue that
658
+ // is checked after all other global queues. In some cases, a work item may queue another work item that is part of
659
+ // the same set of work. For global work items, the second work item would typically get queued behind other global
660
+ // work items. In some cases involving a lot of sync-over-async, that can significantly delay worker threads from
661
+ // getting unblocked.
662
+ if ( tl == null && callback is QueueUserWorkItemCallbackBase )
663
+ {
664
+ queue = lowPriorityWorkItems ;
665
+ }
666
+ else if ( s_assignableWorkItemQueueCount > 0 && tl != null )
667
+ {
668
+ queue = tl . assignedGlobalWorkItemQueue ;
669
+ }
670
+ else
671
+ {
672
+ queue = workItems ;
673
+ }
674
+
675
+ queue . Enqueue ( callback ) ;
676
+ }
677
+ #endif
678
+
618
679
public void EnqueueAtHighPriority ( object workItem )
619
680
{
620
681
Debug . Assert ( ( workItem is IThreadPoolWorkItem ) ^ ( workItem is Task ) ) ;
@@ -691,6 +752,14 @@ internal static bool LocalFindAndPop(object callback)
691
752
}
692
753
}
693
754
755
+ #if CORECLR
756
+ // Check for low-priority work items
757
+ if ( s_prioritizationExperiment && lowPriorityWorkItems . TryDequeue ( out workItem ) )
758
+ {
759
+ return workItem ;
760
+ }
761
+ #endif
762
+
694
763
// Try to steal from other threads' local work items
695
764
{
696
765
WorkStealingQueue localWsq = tl . workStealingQueue ;
@@ -750,6 +819,13 @@ public long GlobalCount
750
819
get
751
820
{
752
821
long count = ( long ) highPriorityWorkItems . Count + workItems . Count ;
822
+ #if CORECLR
823
+ if ( s_prioritizationExperiment )
824
+ {
825
+ count += lowPriorityWorkItems . Count ;
826
+ }
827
+ #endif
828
+
753
829
for ( int i = 0 ; i < s_assignableWorkItemQueueCount ; i ++ )
754
830
{
755
831
count += _assignableWorkItemQueues [ i ] . Count ;
@@ -1639,6 +1715,17 @@ internal static IEnumerable<object> GetQueuedWorkItems()
1639
1715
yield return workItem ;
1640
1716
}
1641
1717
1718
+ #if CORECLR
1719
+ if ( ThreadPoolWorkQueue . s_prioritizationExperiment )
1720
+ {
1721
+ // Enumerate low-priority global queue
1722
+ foreach ( object workItem in s_workQueue . lowPriorityWorkItems )
1723
+ {
1724
+ yield return workItem ;
1725
+ }
1726
+ }
1727
+ #endif
1728
+
1642
1729
// Enumerate each local queue
1643
1730
foreach ( ThreadPoolWorkQueue . WorkStealingQueue wsq in ThreadPoolWorkQueue . WorkStealingQueueList . Queues )
1644
1731
{
0 commit comments