From ae413bc05a6021c5164cc34d899084bc2a2a0579 Mon Sep 17 00:00:00 2001 From: stephentoub Date: Thu, 11 Feb 2016 13:06:41 -0500 Subject: [PATCH] Track TaskSchedulers only when debugger attached Today the base TaskScheduler ctor registers every created scheduler in a ConditionalWeakTable. This is then used in the internal TaskScheduler.GetTaskSchedulersForDebugger() to enable a debugger to find all of the schedulers that are active and then in turn find all tasks scheduled to them. However, for scenarios that involve creating many, many schedulers, this registration adds up to a non-trivial cost, both in time and space, and is only relevant for debugging. Plus, since this mechanism was introduced, Visual Studio has additional ways of tracking tasks for display in the Tasks window, e.g. ETW events. This commit simply makes that registration more pay-for-play, only doing it if the debugger is attached at the time of the scheduler's creation. --- .../src/System/Threading/Tasks/TaskScheduler.cs | 43 ++++++++++++++++++---- .../Threading/Tasks/ThreadPoolTaskScheduler.cs | 1 + 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/mscorlib/src/System/Threading/Tasks/TaskScheduler.cs b/src/mscorlib/src/System/Threading/Tasks/TaskScheduler.cs index 1cb5c45..f824924 100644 --- a/src/mscorlib/src/System/Threading/Tasks/TaskScheduler.cs +++ b/src/mscorlib/src/System/Threading/Tasks/TaskScheduler.cs @@ -278,8 +278,8 @@ namespace System.Threading.Tasks // Member variables // - // The global container that keeps track of TaskScheduler instances. s_activeTaskSchedulers must be initialized before s_defaultTaskScheduler. - private static readonly ConditionalWeakTable s_activeTaskSchedulers = new ConditionalWeakTable(); + // The global container that keeps track of TaskScheduler instances for debugging purposes. + private static ConditionalWeakTable s_activeTaskSchedulers; // An AppDomain-wide default manager. private static readonly TaskScheduler s_defaultTaskScheduler = new ThreadPoolTaskScheduler(); @@ -296,16 +296,32 @@ namespace System.Threading.Tasks // // Constructors and public properties // - + /// /// Initializes the . /// protected TaskScheduler() { - // Protected constructor. It's here to ensure all user implemented TaskSchedulers will be - // registered in the active schedulers list. - Contract.Assert(s_activeTaskSchedulers != null, "Expected non-null s_activeTaskSchedulers"); - s_activeTaskSchedulers.Add(this, null); + // Register the scheduler in the active scheduler list. This is only relevant when debugging, + // so we only pay the cost if the debugger is attached when the scheduler is created. This + // means that the internal TaskScheduler.GetTaskSchedulersForDebugger() will only include + // schedulers created while the debugger is attached. + if (Debugger.IsAttached) + { + AddToActiveTaskSchedulers(); + } + } + + /// Adds this scheduler ot the active schedulers tracking collection for debugging purposes. + private void AddToActiveTaskSchedulers() + { + ConditionalWeakTable activeTaskSchedulers = s_activeTaskSchedulers; + if (activeTaskSchedulers == null) + { + Interlocked.CompareExchange(ref s_activeTaskSchedulers, new ConditionalWeakTable(), null); + activeTaskSchedulers = s_activeTaskSchedulers; + } + activeTaskSchedulers.Add(this, null); } /// @@ -553,9 +569,20 @@ namespace System.Threading.Tasks [SecurityCritical] internal static TaskScheduler[] GetTaskSchedulersForDebugger() { - Contract.Assert(s_activeTaskSchedulers != null, "Expected non-null s_activeTaskSchedulers"); + if (s_activeTaskSchedulers == null) + { + // No schedulers were tracked. Just give back the default. + return new TaskScheduler[] { s_defaultTaskScheduler }; + } ICollection schedulers = s_activeTaskSchedulers.Keys; + if (!schedulers.Contains(s_defaultTaskScheduler)) + { + // Make sure the default is included, in case the debugger attached + // after it was created. + schedulers.Add(s_defaultTaskScheduler); + } + var arr = new TaskScheduler[schedulers.Count]; schedulers.CopyTo(arr, 0); foreach (var scheduler in arr) diff --git a/src/mscorlib/src/System/Threading/Tasks/ThreadPoolTaskScheduler.cs b/src/mscorlib/src/System/Threading/Tasks/ThreadPoolTaskScheduler.cs index e0abcb9..dd4cbc9 100644 --- a/src/mscorlib/src/System/Threading/Tasks/ThreadPoolTaskScheduler.cs +++ b/src/mscorlib/src/System/Threading/Tasks/ThreadPoolTaskScheduler.cs @@ -29,6 +29,7 @@ namespace System.Threading.Tasks /// internal ThreadPoolTaskScheduler() { + int id = base.Id; // force ID creation of the default scheduler } // static delegate for threads allocated to handle LongRunning tasks. -- 2.7.4