Fast-path ExecutionContext for ThreadPool items (#20308)
authorBen Adams <thundercat@illyriad.co.uk>
Fri, 9 Nov 2018 17:26:42 +0000 (17:26 +0000)
committerKoundinya Veluri <kouvel@users.noreply.github.com>
Fri, 9 Nov 2018 17:26:42 +0000 (09:26 -0800)
Fast-path ExecutionContext for ThreadPool items

Maintain the ThreadPool threads on the Default contexts between work items.

Always restore the Default context on the ThreadPool Dispatch loop after a workitem has run (to clean up any ExecutionContext leakage from changes on flow suppressed workitems, or AsyncLocal change eventhandlers; as well as firing their notifications if they have them)

Store the `CurrentThread` as part of the thread static `ThreadPoolWorkQueueThreadLocals` which are already looked up at the start of the Dispatch loop to avoid an additional lookup via `Thread.CurrentThread`.

As workitems are started on the Default context and are returned to it `QueueUserWorkItemCallbackDefaultContext` items can just be run their callbacks directly rather than via `ExecutionContext.Run` (as the EC don't need to move to Default and is always moved back to Default).

As `QueueUserWorkItemCallbackDefaultContext` now runs items directly; flow suppressed callbacks can use the smaller `QueueUserWorkItemCallbackDefaultContext` rather than `QueueUserWorkItemCallback` with a null context; and handling for flow suppression can be removed from `QueueUserWorkItemCallback`.

As `AwaitTaskContinuation`'s `IThreadPoolWorkItem.Execute` doesn't preform additional work after it completes, it can run `m_action` directly for Default context in addition to the flow suppressed context, rather than going via `ExecutionContext.Run`.

Given that the items on the ThreadPool are always started on the threadpool and restored to it; we can introduce some faster paths than `ExecutionContext:RunInternal` (328 bytes asm).

Introduce `ExecutionContext:RunForThreadPoolUnsafe` (71 bytes asm), for `IThreadPoolWorkItem`s where they need to run on a provided context, but do not need to execute anything after they complete so can rely on the Dispatch loop restore. This includes `QueueUserWorkItemCallback`, `QueueUserWorkItemCallback<TState>` and `AwaitTaskContinuation`.

Introduce `ExecutionContext:RunFromThreadPoolDispatchLoop` (225 bytes asm), for items run from the ThreadPool, so don't need to capture current context (Default) to restore later, however need to do need to restore back to Default after execution as they then perform additional work. This includes
`Task`/`AsyncStateMachineBox`/`AwaitTaskContinuation`/`Timer`.

Change `Task.ExecuteFromThreadPool()` to take the thread `Task.ExecuteFromThreadPool(Thread threadPoolThread)` from the ThreadPool Dispatch loop so it can pass it into the `ExecutionContext:RunFromThreadPoolDispatchLoop` overload and avoid the `Thread.CurrentThread` lookup.

Perf test: https://github.com/dotnet/coreclr/pull/20308#issuecomment-436805786

Resolves: dotnet/corefx#32695

src/System.Private.CoreLib/shared/System/Threading/ExecutionContext.cs
src/System.Private.CoreLib/shared/System/Threading/SemaphoreSlim.cs
src/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs
src/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs
src/System.Private.CoreLib/src/System/Threading/Tasks/TaskContinuation.cs
src/System.Private.CoreLib/src/System/Threading/Tasks/ThreadPoolTaskScheduler.cs
src/System.Private.CoreLib/src/System/Threading/ThreadPool.cs
src/System.Private.CoreLib/src/System/Threading/Timer.cs

index 983281491778ebee2cd9c4bd23594a373557e381..5c1d1a1c9ca860945a85e2d2f3008e84cc77cb22 100644 (file)
@@ -12,6 +12,7 @@
 ===========================================================*/
 
 using System.Diagnostics;
+using System.Runtime.CompilerServices;
 using System.Runtime.ExceptionServices;
 using System.Runtime.Serialization;
 
@@ -136,6 +137,11 @@ namespace System.Threading
             Thread currentThread0 = Thread.CurrentThread;
             Thread currentThread = currentThread0;
             ExecutionContext previousExecutionCtx0 = currentThread0.ExecutionContext;
+            if (previousExecutionCtx0 != null && previousExecutionCtx0.m_isDefault)
+            {
+                // Default is a null ExecutionContext internally
+                previousExecutionCtx0 = null;
+            }
 
             // Store current ExecutionContext and SynchronizationContext as "previousXxx".
             // This allows us to restore them and undo any Context changes made in callback.Invoke
@@ -215,6 +221,11 @@ namespace System.Threading
             Thread currentThread0 = Thread.CurrentThread;
             Thread currentThread = currentThread0;
             ExecutionContext previousExecutionCtx0 = currentThread0.ExecutionContext;
+            if (previousExecutionCtx0 != null && previousExecutionCtx0.m_isDefault)
+            {
+                // Default is a null ExecutionContext internally
+                previousExecutionCtx0 = null;
+            }
 
             // Store current ExecutionContext and SynchronizationContext as "previousXxx".
             // This allows us to restore them and undo any Context changes made in callback.Invoke
@@ -282,6 +293,115 @@ namespace System.Threading
             edi?.Throw();
         }
 
+        internal static void RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, object state)
+        {
+            Debug.Assert(threadPoolThread == Thread.CurrentThread);
+            CheckThreadPoolAndContextsAreDefault();
+            // ThreadPool starts on Default Context so we don't need to save the "previous" state as we know it is Default (null)
+
+            if (executionContext != null && executionContext.m_isDefault)
+            {
+                // Default is a null ExecutionContext internally
+                executionContext = null;
+            }
+            else if (executionContext != null)
+            {
+                // Non-Default context to restore
+                threadPoolThread.ExecutionContext = executionContext;
+                if (executionContext.HasChangeNotifications)
+                {
+                    // There are change notifications; trigger any affected
+                    OnValuesChanged(previousExecutionCtx: null, executionContext);
+                }
+            }
+
+            ExceptionDispatchInfo edi = null;
+            try
+            {
+                callback.Invoke(state);
+            }
+            catch (Exception ex)
+            {
+                // Note: we have a "catch" rather than a "finally" because we want
+                // to stop the first pass of EH here.  That way we can restore the previous
+                // context before any of our callers' EH filters run.
+                edi = ExceptionDispatchInfo.Capture(ex);
+            }
+
+            // Enregister threadPoolThread as it crossed EH, and use enregistered variable
+            Thread currentThread = threadPoolThread;
+
+            ExecutionContext currentExecutionCtx = currentThread.ExecutionContext;
+
+            // Restore changed SynchronizationContext back to Default
+            currentThread.SynchronizationContext = null;
+            if (currentExecutionCtx != null)
+            {
+                // The EC always needs to be reset for this overload, as it will flow back to the caller if it performs 
+                // extra work prior to returning to the Dispatch loop. For example for Task-likes it will flow out of await points
+
+                // Restore to Default before Notifications, as the change can be observed in the handler.
+                currentThread.ExecutionContext = null;
+                if (currentExecutionCtx.HasChangeNotifications)
+                {
+                    // There are change notifications; trigger any affected
+                    OnValuesChanged(currentExecutionCtx, nextExecutionCtx: null);
+                }
+            }
+
+            // If exception was thrown by callback, rethrow it now original contexts are restored
+            edi?.Throw();
+        }
+
+        internal static void RunForThreadPoolUnsafe<TState>(ExecutionContext executionContext, Action<TState> callback, in TState state)
+        {
+            // We aren't running in try/catch as if an exception is directly thrown on the ThreadPool either process 
+            // will crash or its a ThreadAbortException. 
+
+            CheckThreadPoolAndContextsAreDefault();
+            Debug.Assert(executionContext != null && !executionContext.m_isDefault, "ExecutionContext argument is Default.");
+
+            Thread currentThread = Thread.CurrentThread;
+            // Restore Non-Default context
+            currentThread.ExecutionContext = executionContext;
+            if (executionContext.HasChangeNotifications)
+            {
+                OnValuesChanged(previousExecutionCtx: null, executionContext);
+            }
+
+            callback.Invoke(state);
+
+            // ThreadPoolWorkQueue.Dispatch will handle notifications and reset EC and SyncCtx back to default
+        }
+
+        // Inline as only called in one place and always called
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal static void ResetThreadPoolThread(Thread currentThread)
+        {
+            ExecutionContext currentExecutionCtx = currentThread.ExecutionContext;
+
+            // Reset to defaults
+            currentThread.SynchronizationContext = null;
+            currentThread.ExecutionContext = null;
+
+            if (currentExecutionCtx != null && currentExecutionCtx.HasChangeNotifications)
+            {
+                OnValuesChanged(currentExecutionCtx, nextExecutionCtx: null);
+
+                // Reset to defaults again without change notifications in case the Change handler changed the contexts
+                currentThread.SynchronizationContext = null;
+                currentThread.ExecutionContext = null;
+            }
+        }
+
+        [System.Diagnostics.Conditional("DEBUG")]
+        internal static void CheckThreadPoolAndContextsAreDefault()
+        {
+            Debug.Assert(Thread.CurrentThread.IsThreadPoolThread);
+            Debug.Assert(Thread.CurrentThread.ExecutionContext == null, "ThreadPool thread not on Default ExecutionContext.");
+            Debug.Assert(Thread.CurrentThread.SynchronizationContext == null, "ThreadPool thread not on Default SynchronizationContext.");
+        }
+
         internal static void OnValuesChanged(ExecutionContext previousExecutionCtx, ExecutionContext nextExecutionCtx)
         {
             Debug.Assert(previousExecutionCtx != nextExecutionCtx);
index 40925dd31a0304ea9b10f394338d19dd5a97aa88..41f6c7e96a97525f8931e006027817511d017b2f 100644 (file)
@@ -80,7 +80,7 @@ namespace System.Threading
             internal TaskNode Prev, Next;
             internal TaskNode() : base() { }
 
-            internal override void ExecuteFromThreadPool()
+            internal override void ExecuteFromThreadPool(Thread threadPoolThread)
             {
                 bool setSuccessfully = TrySetResult(true);
                 Debug.Assert(setSuccessfully, "Should have been able to complete task");
index fc40c922252c6f0e49ee5ee99187710490bdbe67..e85f89b58f643670b82baeb8d1be2bdcf4bc4b59 100644 (file)
@@ -540,10 +540,12 @@ namespace System.Runtime.CompilerServices
             /// <summary>A delegate to the <see cref="MoveNext"/> method.</summary>
             public Action MoveNextAction => _moveNextAction ?? (_moveNextAction = new Action(MoveNext));
 
-            internal sealed override void ExecuteFromThreadPool() => MoveNext();
+            internal sealed override void ExecuteFromThreadPool(Thread threadPoolThread) => MoveNext(threadPoolThread);
 
             /// <summary>Calls MoveNext on <see cref="StateMachine"/></summary>
-            public void MoveNext()
+            public void MoveNext() => MoveNext(threadPoolThread: null);
+
+            private void MoveNext(Thread threadPoolThread)
             {
                 Debug.Assert(!IsCompleted);
 
@@ -560,7 +562,14 @@ namespace System.Runtime.CompilerServices
                 }
                 else
                 {
-                    ExecutionContext.RunInternal(context, s_callback, this);
+                    if (threadPoolThread is null)
+                    {
+                        ExecutionContext.RunInternal(context, s_callback, this);
+                    }
+                    else
+                    {
+                        ExecutionContext.RunFromThreadPoolDispatchLoop(threadPoolThread, context, s_callback, this);
+                    }
                 }
 
                 if (IsCompleted)
index f78dcd0b1e64a194e532a060b35eabe12d052eef..7db6cdbac7303a24a84ccfd9d710f6273cedb407 100644 (file)
@@ -2368,16 +2368,16 @@ namespace System.Threading.Tasks
         /// can override to customize their behavior, which is usually done by promises
         /// that want to reuse the same object as a queued work item.
         /// </summary>
-        internal virtual void ExecuteFromThreadPool() => ExecuteEntryUnsafe();
+        internal virtual void ExecuteFromThreadPool(Thread threadPoolThread) => ExecuteEntryUnsafe(threadPoolThread);
 
-        internal void ExecuteEntryUnsafe() // used instead of ExecuteEntry() when we don't have to worry about double-execution prevent
+        internal void ExecuteEntryUnsafe(Thread threadPoolThread) // used instead of ExecuteEntry() when we don't have to worry about double-execution prevent
         {
             // Remember that we started running the task delegate.
             m_stateFlags |= TASK_STATE_DELEGATE_INVOKED;
 
             if (!IsCancellationRequested & !IsCanceled)
             {
-                ExecuteWithThreadLocal(ref t_currentTask);
+                ExecuteWithThreadLocal(ref t_currentTask, threadPoolThread);
             }
             else
             {
@@ -2398,7 +2398,7 @@ namespace System.Threading.Tasks
         }
 
         // A trick so we can refer to the TLS slot with a byref.
-        private void ExecuteWithThreadLocal(ref Task currentTaskSlot)
+        private void ExecuteWithThreadLocal(ref Task currentTaskSlot, Thread threadPoolThread = null)
         {
             // Remember the current task so we can restore it after running, and then
             Task previousTask = currentTaskSlot;
@@ -2439,7 +2439,14 @@ namespace System.Threading.Tasks
                     else
                     {
                         // Invoke it under the captured ExecutionContext
-                        ExecutionContext.RunInternal(ec, s_ecCallback, this);
+                        if (threadPoolThread is null)
+                        {
+                            ExecutionContext.RunInternal(ec, s_ecCallback, this);
+                        }
+                        else
+                        {
+                            ExecutionContext.RunFromThreadPoolDispatchLoop(threadPoolThread, ec, s_ecCallback, this);
+                        }
                     }
                 }
                 catch (Exception exn)
index ab5859507f25d8744c724b752b410121f309a030..e8eb1b938200ec36860f492340b9df552ebcab8d 100644 (file)
@@ -646,16 +646,20 @@ namespace System.Threading.Tasks
                 // We're not inside of a task, so t_currentTask doesn't need to be specially maintained.
                 // We're on a thread pool thread with no higher-level callers, so exceptions can just propagate.
 
-                // If there's no execution context, just invoke the delegate.
-                if (context == null)
+                ExecutionContext.CheckThreadPoolAndContextsAreDefault();
+                // If there's no execution context or Default, just invoke the delegate as ThreadPool is on Default context.
+                // We don't have to use ExecutionContext.Run for the Default context here as there is no extra processing after the delegate
+                if (context == null || context.IsDefault)
                 {
                     m_action();
                 }
                 // If there is an execution context, get the cached delegate and run the action under the context.
                 else
                 {
-                    ExecutionContext.RunInternal(context, GetInvokeActionCallback(), m_action);
+                    ExecutionContext.RunForThreadPoolUnsafe(context, s_invokeAction, m_action);
                 }
+
+                // ThreadPoolWorkQueue.Dispatch handles notifications and reset context back to default
             }
             finally
             {
@@ -667,19 +671,11 @@ namespace System.Threading.Tasks
         }
 
         /// <summary>Cached delegate that invokes an Action passed as an object parameter.</summary>
-        private static ContextCallback s_invokeActionCallback;
-
-        /// <summary>Runs an action provided as an object parameter.</summary>
-        /// <param name="state">The Action to invoke.</param>
-        private static void InvokeAction(object state) { ((Action)state)(); }
+        private readonly static ContextCallback s_invokeContextCallback = (state) => ((Action)state)();
+        private readonly static Action<Action> s_invokeAction = (action) => action();
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        protected static ContextCallback GetInvokeActionCallback()
-        {
-            ContextCallback callback = s_invokeActionCallback;
-            if (callback == null) { s_invokeActionCallback = callback = InvokeAction; } // lazily initialize SecurityCritical delegate
-            return callback;
-        }
+        protected static ContextCallback GetInvokeActionCallback() => s_invokeContextCallback;
 
         /// <summary>Runs the callback synchronously with the provided state.</summary>
         /// <param name="callback">The callback to run.</param>
index 93aa26c721820f97581997236bb1a48aedbce6f2..9ab28ef34ae2bd7e95ebce768928a658afd6830c 100644 (file)
@@ -33,7 +33,7 @@ namespace System.Threading.Tasks
         }
 
         // static delegate for threads allocated to handle LongRunning tasks.
-        private static readonly ParameterizedThreadStart s_longRunningThreadWork = s => ((Task)s).ExecuteEntryUnsafe();
+        private static readonly ParameterizedThreadStart s_longRunningThreadWork = s => ((Task)s).ExecuteEntryUnsafe(threadPoolThread: null);
 
         /// <summary>
         /// Schedules a task to the ThreadPool.
@@ -73,7 +73,7 @@ namespace System.Threading.Tasks
 
             try
             {
-                task.ExecuteEntryUnsafe(); // handles switching Task.Current etc.
+                task.ExecuteEntryUnsafe(threadPoolThread: null); // handles switching Task.Current etc.
             }
             finally
             {
index b8ed9141b882f42746d3e368db50aa78865f6ea0..564db933450a278cac51b0cb87e3307d36a04858 100644 (file)
@@ -500,7 +500,7 @@ namespace System.Threading
 
         internal static bool Dispatch()
         {
-            var workQueue = ThreadPoolGlobals.workQueue;
+            ThreadPoolWorkQueue outerWorkQueue = ThreadPoolGlobals.workQueue;
             //
             // The clock is ticking!  We have ThreadPoolGlobals.TP_QUANTUM milliseconds to get some work done, and then
             // we need to return to the VM.
@@ -515,23 +515,30 @@ namespace System.Threading
             // Note that if this thread is aborted before we get a chance to request another one, the VM will
             // record a thread request on our behalf.  So we don't need to worry about getting aborted right here.
             //
-            workQueue.MarkThreadRequestSatisfied();
+            outerWorkQueue.MarkThreadRequestSatisfied();
 
             // Has the desire for logging changed since the last time we entered?
-            workQueue.loggingEnabled = FrameworkEventSource.Log.IsEnabled(EventLevel.Verbose, FrameworkEventSource.Keywords.ThreadPool | FrameworkEventSource.Keywords.ThreadTransfer);
+            outerWorkQueue.loggingEnabled = FrameworkEventSource.Log.IsEnabled(EventLevel.Verbose, FrameworkEventSource.Keywords.ThreadPool | FrameworkEventSource.Keywords.ThreadTransfer);
 
             //
             // Assume that we're going to need another thread if this one returns to the VM.  We'll set this to 
             // false later, but only if we're absolutely certain that the queue is empty.
             //
             bool needAnotherThread = true;
-            object workItem = null;
+            object outerWorkItem = null;
             try
             {
                 //
                 // Set up our thread-local data
                 //
+                // Use operate on workQueue local to try block so it can be enregistered 
+                ThreadPoolWorkQueue workQueue = outerWorkQueue;
                 ThreadPoolWorkQueueThreadLocals tl = workQueue.EnsureCurrentThreadHasQueue();
+                Thread currentThread = tl.currentThread;
+
+                // Start on clean ExecutionContext and SynchronizationContext
+                currentThread.ExecutionContext = null;
+                currentThread.SynchronizationContext = null;
 
                 //
                 // Loop until our quantum expires.
@@ -539,7 +546,8 @@ namespace System.Threading
                 while ((Environment.TickCount - quantumStartTime) < ThreadPoolGlobals.TP_QUANTUM)
                 {
                     bool missedSteal = false;
-                    workItem = workQueue.Dequeue(tl, ref missedSteal);
+                    // Use operate on workItem local to try block so it can be enregistered 
+                    object workItem = outerWorkItem = workQueue.Dequeue(tl, ref missedSteal);
 
                     if (workItem == null)
                     {
@@ -578,7 +586,7 @@ namespace System.Threading
                             reportedStatus = true;
                             if (workItem is Task task)
                             {
-                                task.ExecuteFromThreadPool();
+                                task.ExecuteFromThreadPool(currentThread);
                             }
                             else
                             {
@@ -598,14 +606,19 @@ namespace System.Threading
                         // for Task and then Unsafe.As for the interface, rather than
                         // vice versa, in particular when the object implements a bunch
                         // of interfaces.
-                        task.ExecuteFromThreadPool();
+                        task.ExecuteFromThreadPool(currentThread);
                     }
                     else
                     {
                         Debug.Assert(workItem is IThreadPoolWorkItem);
                         Unsafe.As<IThreadPoolWorkItem>(workItem).Execute();
                     }
-                    workItem = null;
+
+                    // Release refs
+                    outerWorkItem = workItem = null;
+
+                    // Return to clean ExecutionContext and SynchronizationContext
+                    ExecutionContext.ResetThreadPoolThread(currentThread);
 
                     // 
                     // Notify the VM that we executed this workitem.  This is also our opportunity to ask whether Hill Climbing wants
@@ -626,7 +639,7 @@ namespace System.Threading
                 // it was executed or not (in debug builds only).  Task uses this to communicate the ThreadAbortException to anyone
                 // who waits for the task to complete.
                 //
-                if (workItem is Task task)
+                if (outerWorkItem is Task task)
                 {
                     task.MarkAbortedFromThreadPool(tae);
                 }
@@ -643,7 +656,7 @@ namespace System.Threading
                 // thread to pick up where we left off.
                 //
                 if (needAnotherThread)
-                    workQueue.EnsureThreadRequested();
+                    outerWorkQueue.EnsureThreadRequested();
             }
 
             // 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.
@@ -685,6 +698,7 @@ namespace System.Threading
 
         public readonly ThreadPoolWorkQueue workQueue;
         public readonly ThreadPoolWorkQueue.WorkStealingQueue workStealingQueue;
+        public readonly Thread currentThread;
         public FastRandom random = new FastRandom(Thread.CurrentThread.ManagedThreadId); // mutable struct, do not copy or make readonly
 
         public ThreadPoolWorkQueueThreadLocals(ThreadPoolWorkQueue tpq)
@@ -692,6 +706,7 @@ namespace System.Threading
             workQueue = tpq;
             workStealingQueue = new ThreadPoolWorkQueue.WorkStealingQueue();
             ThreadPoolWorkQueue.WorkStealingQueueList.Add(workStealingQueue);
+            currentThread = Thread.CurrentThread;
         }
 
         private void CleanUp()
@@ -937,17 +952,18 @@ namespace System.Threading
         private readonly object _state;
         private readonly ExecutionContext _context;
 
-        internal static readonly ContextCallback s_executionContextShim = state =>
+        private static readonly Action<QueueUserWorkItemCallback> s_executionContextShim = quwi =>
         {
-            var obj = (QueueUserWorkItemCallback)state;
-            WaitCallback c = obj._callback;
-            Debug.Assert(c != null);
-            obj._callback = null;
-            c(obj._state);
+            WaitCallback callback = quwi._callback;
+            quwi._callback = null;
+
+            callback(quwi._state);
         };
 
         internal QueueUserWorkItemCallback(WaitCallback callback, object state, ExecutionContext context)
         {
+            Debug.Assert(context != null);
+
             _callback = callback;
             _state = state;
             _context = context;
@@ -956,17 +972,8 @@ namespace System.Threading
         public override void Execute()
         {
             base.Execute();
-            ExecutionContext context = _context;
-            if (context == null)
-            {
-                WaitCallback c = _callback;
-                _callback = null;
-                c(_state);
-            }
-            else
-            {
-                ExecutionContext.RunInternal(context, s_executionContextShim, this);
-            }
+
+            ExecutionContext.RunForThreadPoolUnsafe(_context, s_executionContextShim, this);
         }
     }
 
@@ -976,17 +983,10 @@ namespace System.Threading
         private readonly TState _state;
         private readonly ExecutionContext _context;
 
-        internal static readonly ContextCallback s_executionContextShim = state =>
-        {
-            var obj = (QueueUserWorkItemCallback<TState>)state;
-            Action<TState> c = obj._callback;
-            Debug.Assert(c != null);
-            obj._callback = null;
-            c(obj._state);
-        };
-
         internal QueueUserWorkItemCallback(Action<TState> callback, TState state, ExecutionContext context)
         {
+            Debug.Assert(callback != null);
+
             _callback = callback;
             _state = state;
             _context = context;
@@ -995,17 +995,11 @@ namespace System.Threading
         public override void Execute()
         {
             base.Execute();
-            ExecutionContext context = _context;
-            if (context == null)
-            {
-                Action<TState> c = _callback;
-                _callback = null;
-                c(_state);
-            }
-            else
-            {
-                ExecutionContext.RunInternal(context, s_executionContextShim, this);
-            }
+
+            Action<TState> callback = _callback;
+            _callback = null;
+
+            ExecutionContext.RunForThreadPoolUnsafe(_context, callback, in _state);
         }
     }
 
@@ -1014,25 +1008,25 @@ namespace System.Threading
         private WaitCallback _callback; // SOS's ThreadPool command depends on this name
         private readonly object _state;
 
-        internal static readonly ContextCallback s_executionContextShim = state =>
-        {
-            var obj = (QueueUserWorkItemCallbackDefaultContext)state;
-            WaitCallback c = obj._callback;
-            Debug.Assert(c != null);
-            obj._callback = null;
-            c(obj._state);
-        };
-
         internal QueueUserWorkItemCallbackDefaultContext(WaitCallback callback, object state)
         {
+            Debug.Assert(callback != null);
+
             _callback = callback;
             _state = state;
         }
 
         public override void Execute()
         {
+            ExecutionContext.CheckThreadPoolAndContextsAreDefault();
             base.Execute();
-            ExecutionContext.RunInternal(executionContext: null, s_executionContextShim, this); // null executionContext on RunInternal is Default context
+
+            WaitCallback callback = _callback;
+            _callback = null;
+
+            callback(_state);
+
+            // ThreadPoolWorkQueue.Dispatch will handle notifications and reset EC and SyncCtx back to default
         }
     }
 
@@ -1041,25 +1035,25 @@ namespace System.Threading
         private Action<TState> _callback; // SOS's ThreadPool command depends on this name
         private readonly TState _state;
 
-        internal static readonly ContextCallback s_executionContextShim = state =>
-        {
-            var obj = (QueueUserWorkItemCallbackDefaultContext<TState>)state;
-            Action<TState> c = obj._callback;
-            Debug.Assert(c != null);
-            obj._callback = null;
-            c(obj._state);
-        };
-
         internal QueueUserWorkItemCallbackDefaultContext(Action<TState> callback, TState state)
         {
+            Debug.Assert(callback != null);
+
             _callback = callback;
             _state = state;
         }
 
         public override void Execute()
         {
+            ExecutionContext.CheckThreadPoolAndContextsAreDefault();
             base.Execute();
-            ExecutionContext.RunInternal(executionContext: null, s_executionContextShim, this); // null executionContext on RunInternal is Default context
+
+            Action<TState> callback = _callback;
+            _callback = null;
+
+            callback(_state);
+
+            // ThreadPoolWorkQueue.Dispatch will handle notifications and reset EC and SyncCtx back to default
         }
     }
 
@@ -1303,7 +1297,7 @@ namespace System.Threading
 
             ExecutionContext context = ExecutionContext.Capture();
 
-            object tpcallBack = (context != null && context.IsDefault) ?
+            object tpcallBack = (context == null || context.IsDefault) ?
                 new QueueUserWorkItemCallbackDefaultContext(callBack, state) :
                 (object)new QueueUserWorkItemCallback(callBack, state, context);
 
@@ -1323,7 +1317,7 @@ namespace System.Threading
 
             ExecutionContext context = ExecutionContext.Capture();
 
-            object tpcallBack = (context != null && context.IsDefault) ?
+            object tpcallBack = (context == null || context.IsDefault) ?
                 new QueueUserWorkItemCallbackDefaultContext<TState>(callBack, state) :
                 (object)new QueueUserWorkItemCallback<TState>(callBack, state, context);
 
@@ -1343,7 +1337,7 @@ namespace System.Threading
             EnsureVMInitialized();
 
             ThreadPoolGlobals.workQueue.Enqueue(
-                new QueueUserWorkItemCallback<TState>(callBack, state, null), forceGlobal: !preferLocal);
+                new QueueUserWorkItemCallbackDefaultContext<TState>(callBack, state), forceGlobal: !preferLocal);
 
             return true;
         }
@@ -1357,7 +1351,7 @@ namespace System.Threading
 
             EnsureVMInitialized();
 
-            object tpcallBack = new QueueUserWorkItemCallback(callBack, state, null);
+            object tpcallBack = new QueueUserWorkItemCallbackDefaultContext(callBack, state);
 
             ThreadPoolGlobals.workQueue.Enqueue(tpcallBack, forceGlobal: true);
 
index 8eb2310c3d557a13508e61103ed8b76e4f121289..0bc235db6eb48c3ae56a709d031d0fdc87e29578 100644 (file)
@@ -674,9 +674,9 @@ namespace System.Threading
             }
         }
 
-        void IThreadPoolWorkItem.Execute() => Fire();
+        void IThreadPoolWorkItem.Execute() => Fire(isThreadPool: true);
 
-        internal void Fire()
+        internal void Fire(bool isThreadPool = false)
         {
             bool canceled = false;
 
@@ -690,7 +690,7 @@ namespace System.Threading
             if (canceled)
                 return;
 
-            CallCallback();
+            CallCallback(isThreadPool);
 
             bool shouldSignal = false;
             lock (m_associatedTimerQueue)
@@ -719,7 +719,7 @@ namespace System.Threading
             }
         }
 
-        internal void CallCallback()
+        internal void CallCallback(bool isThreadPool)
         {
             if (FrameworkEventSource.IsInitialized && FrameworkEventSource.Log.IsEnabled(EventLevel.Informational, FrameworkEventSource.Keywords.ThreadTransfer))
                 FrameworkEventSource.Log.ThreadTransferReceiveObj(this, 1, string.Empty);
@@ -732,7 +732,14 @@ namespace System.Threading
             }
             else
             {
-                ExecutionContext.RunInternal(context, s_callCallbackInContext, this);
+                if (isThreadPool)
+                {
+                    ExecutionContext.RunFromThreadPoolDispatchLoop(Thread.CurrentThread, context, s_callCallbackInContext, this);
+                }
+                else
+                {
+                    ExecutionContext.RunInternal(context, s_callCallbackInContext, this);
+                }
             }
         }