===========================================================*/
using System.Diagnostics;
+using System.Runtime.CompilerServices;
using System.Runtime.ExceptionServices;
using System.Runtime.Serialization;
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
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
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);
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");
/// <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);
}
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)
/// 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
{
}
// 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;
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)
// 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
{
}
/// <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>
}
// 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.
try
{
- task.ExecuteEntryUnsafe(); // handles switching Task.Current etc.
+ task.ExecuteEntryUnsafe(threadPoolThread: null); // handles switching Task.Current etc.
}
finally
{
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.
// 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.
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)
{
reportedStatus = true;
if (workItem is Task task)
{
- task.ExecuteFromThreadPool();
+ task.ExecuteFromThreadPool(currentThread);
}
else
{
// 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
// 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);
}
// 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.
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)
workQueue = tpq;
workStealingQueue = new ThreadPoolWorkQueue.WorkStealingQueue();
ThreadPoolWorkQueue.WorkStealingQueueList.Add(workStealingQueue);
+ currentThread = Thread.CurrentThread;
}
private void CleanUp()
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;
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);
}
}
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;
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);
}
}
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
}
}
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
}
}
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);
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);
EnsureVMInitialized();
ThreadPoolGlobals.workQueue.Enqueue(
- new QueueUserWorkItemCallback<TState>(callBack, state, null), forceGlobal: !preferLocal);
+ new QueueUserWorkItemCallbackDefaultContext<TState>(callBack, state), forceGlobal: !preferLocal);
return true;
}
EnsureVMInitialized();
- object tpcallBack = new QueueUserWorkItemCallback(callBack, state, null);
+ object tpcallBack = new QueueUserWorkItemCallbackDefaultContext(callBack, state);
ThreadPoolGlobals.workQueue.Enqueue(tpcallBack, forceGlobal: true);
}
}
- void IThreadPoolWorkItem.Execute() => Fire();
+ void IThreadPoolWorkItem.Execute() => Fire(isThreadPool: true);
- internal void Fire()
+ internal void Fire(bool isThreadPool = false)
{
bool canceled = false;
if (canceled)
return;
- CallCallback();
+ CallCallback(isThreadPool);
bool shouldSignal = false;
lock (m_associatedTimerQueue)
}
}
- 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);
}
else
{
- ExecutionContext.RunInternal(context, s_callCallbackInContext, this);
+ if (isThreadPool)
+ {
+ ExecutionContext.RunFromThreadPoolDispatchLoop(Thread.CurrentThread, context, s_callCallbackInContext, this);
+ }
+ else
+ {
+ ExecutionContext.RunInternal(context, s_callCallbackInContext, this);
+ }
}
}