Reduce Execution Context Save+Restore (#15629)
authorBen Adams <thundercat@illyriad.co.uk>
Wed, 31 Jan 2018 15:24:29 +0000 (15:24 +0000)
committerStephen Toub <stoub@microsoft.com>
Wed, 31 Jan 2018 15:24:29 +0000 (10:24 -0500)
* Reduce Save+Restore for ExecutionContext

* Use flag rather than comparison to static

* Skip null check for pre-checked EC.Run

* Feedback

* Add static helper lookup for default context for TP

* Add note for enregistering

* Return to Default context when no values

* Default + FlowSuppressed Context

* Move AsyncMethodBuilder.Start to static non-generic

* Feedback

14 files changed:
src/mscorlib/shared/System.Private.CoreLib.Shared.projitems
src/mscorlib/shared/System/Runtime/CompilerServices/AsyncMethodBuilder.cs [new file with mode: 0644]
src/mscorlib/shared/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs
src/mscorlib/shared/System/Threading/AsyncLocal.cs
src/mscorlib/shared/System/Threading/ExecutionContext.cs
src/mscorlib/src/System/IO/Stream.cs
src/mscorlib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs
src/mscorlib/src/System/Threading/CancellationTokenSource.cs
src/mscorlib/src/System/Threading/Overlapped.cs
src/mscorlib/src/System/Threading/Tasks/Task.cs
src/mscorlib/src/System/Threading/Tasks/TaskContinuation.cs
src/mscorlib/src/System/Threading/Thread.cs
src/mscorlib/src/System/Threading/ThreadPool.cs
src/mscorlib/src/System/Threading/Timer.cs

index ec576aa..bb1a284 100644 (file)
     <Compile Include="$(MSBuildThisFileDirectory)System\Resources\SatelliteContractVersionAttribute.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Resources\UltimateResourceFallbackLocation.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\AccessedThroughPropertyAttribute.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\AsyncMethodBuilder.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\AsyncMethodBuilderAttribute.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\AsyncStateMachineAttribute.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\AsyncValueTaskMethodBuilder.cs" />
diff --git a/src/mscorlib/shared/System/Runtime/CompilerServices/AsyncMethodBuilder.cs b/src/mscorlib/shared/System/Runtime/CompilerServices/AsyncMethodBuilder.cs
new file mode 100644 (file)
index 0000000..8fa5b54
--- /dev/null
@@ -0,0 +1,68 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Diagnostics;
+using System.Threading;
+
+namespace System.Runtime.CompilerServices
+{
+    internal static partial class AsyncMethodBuilder
+    {
+        /// <summary>Initiates the builder's execution with the associated state machine.</summary>
+        /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
+        /// <param name="stateMachine">The state machine instance, passed by reference.</param>
+        [DebuggerStepThrough]
+        public static void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
+        {
+            if (stateMachine == null) // TStateMachines are generally non-nullable value types, so this check will be elided
+            {
+                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.stateMachine);
+            }
+
+            // enregistrer variables with 0 post-fix so they can be used in registers without EH forcing them to stack
+            // Capture references to Thread Contexts
+            Thread currentThread0 = Thread.CurrentThread;
+            Thread currentThread = currentThread0;
+            ExecutionContext previousExecutionCtx0 = currentThread0.ExecutionContext;
+
+            // Store current ExecutionContext and SynchronizationContext as "previousXxx".
+            // This allows us to restore them and undo any Context changes made in stateMachine.MoveNext
+            // so that they won't "leak" out of the first await.
+            ExecutionContext previousExecutionCtx = previousExecutionCtx0;
+            SynchronizationContext previousSyncCtx = currentThread0.SynchronizationContext;
+
+            try
+            {
+                stateMachine.MoveNext();
+            }
+            finally
+            {
+                // Re-enregistrer variables post EH with 1 post-fix so they can be used in registers rather than from stack
+                SynchronizationContext previousSyncCtx1 = previousSyncCtx;
+                Thread currentThread1 = currentThread;
+                // The common case is that these have not changed, so avoid the cost of a write barrier if not needed.
+                if (previousSyncCtx1 != currentThread1.SynchronizationContext)
+                {
+                    // Restore changed SynchronizationContext back to previous
+                    currentThread1.SynchronizationContext = previousSyncCtx1;
+                }
+
+                ExecutionContext previousExecutionCtx1 = previousExecutionCtx;
+                ExecutionContext currentExecutionCtx1 = currentThread1.ExecutionContext;
+                if (previousExecutionCtx1 != currentExecutionCtx1)
+                {
+                    // Restore changed ExecutionContext back to previous
+                    currentThread1.ExecutionContext = previousExecutionCtx1;
+                    if ((currentExecutionCtx1 != null && currentExecutionCtx1.HasChangeNotifications) ||
+                        (previousExecutionCtx1 != null && previousExecutionCtx1.HasChangeNotifications))
+                    {
+                        // There are change notifications; trigger any affected
+                        ExecutionContext.OnValuesChanged(currentExecutionCtx1, previousExecutionCtx1);
+                    }
+                }
+            }
+        }
+    }
+}
index b5ecd79..23da48e 100644 (file)
@@ -38,8 +38,9 @@ namespace System.Runtime.CompilerServices
         /// <summary>Begins running the builder with the associated state machine.</summary>
         /// <typeparam name="TStateMachine">The type of the state machine.</typeparam>
         /// <param name="stateMachine">The state machine instance, passed by reference.</param>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine =>
-            _methodBuilder.Start(ref stateMachine); // will provide the right ExecutionContext semantics
+            AsyncMethodBuilder.Start(ref stateMachine); // will provide the right ExecutionContext semantics
 
         /// <summary>Associates the builder with the specified state machine.</summary>
         /// <param name="stateMachine">The state machine instance to associate with the builder.</param>
index 59c8fb3..3531265 100644 (file)
@@ -125,7 +125,7 @@ namespace System.Threading
         public static IAsyncLocalValueMap Empty { get; } = new EmptyAsyncLocalValueMap();
 
         // Instance without any key/value pairs.  Used as a singleton/
-        private sealed class EmptyAsyncLocalValueMap : IAsyncLocalValueMap
+        internal sealed class EmptyAsyncLocalValueMap : IAsyncLocalValueMap
         {
             public IAsyncLocalValueMap Set(IAsyncLocal key, object value)
             {
@@ -144,7 +144,7 @@ namespace System.Threading
         }
 
         // Instance with one key/value pair.
-        private sealed class OneElementAsyncLocalValueMap : IAsyncLocalValueMap
+        internal sealed class OneElementAsyncLocalValueMap : IAsyncLocalValueMap
         {
             private readonly IAsyncLocal _key1;
             private readonly object _value1;
index 2d5f5be..9f27c6d 100644 (file)
@@ -21,40 +21,18 @@ namespace System.Threading
 {
     public delegate void ContextCallback(Object state);
 
-    internal struct ExecutionContextSwitcher
-    {
-        internal ExecutionContext m_ec;
-        internal SynchronizationContext m_sc;
-
-        internal void Undo(Thread currentThread)
-        {
-            Debug.Assert(currentThread == Thread.CurrentThread);
-
-            // The common case is that these have not changed, so avoid the cost of a write if not needed.
-            if (currentThread.SynchronizationContext != m_sc)
-            {
-                currentThread.SynchronizationContext = m_sc;
-            }
-
-            if (currentThread.ExecutionContext != m_ec)
-            {
-                ExecutionContext.Restore(currentThread, m_ec);
-            }
-        }
-    }
-
     public sealed class ExecutionContext : IDisposable, ISerializable
     {
-        internal static readonly ExecutionContext Default = new ExecutionContext();
+        internal static readonly ExecutionContext Default = new ExecutionContext(isDefault: true);
 
         private readonly IAsyncLocalValueMap m_localValues;
         private readonly IAsyncLocal[] m_localChangeNotifications;
         private readonly bool m_isFlowSuppressed;
+        private readonly bool m_isDefault;
 
-        private ExecutionContext()
+        private ExecutionContext(bool isDefault)
         {
-            m_localValues = AsyncLocalValueMap.Empty;
-            m_localChangeNotifications = Array.Empty<IAsyncLocal>();
+            m_isDefault = isDefault;
         }
 
         private ExecutionContext(
@@ -86,12 +64,14 @@ namespace System.Threading
             Debug.Assert(isFlowSuppressed != m_isFlowSuppressed);
 
             if (!isFlowSuppressed &&
-                m_localValues == Default.m_localValues &&
-                m_localChangeNotifications == Default.m_localChangeNotifications)
+                (m_localValues == null ||
+                 m_localValues.GetType() == typeof(AsyncLocalValueMap.EmptyAsyncLocalValueMap))
+               )
             {
                 return null; // implies the default context
             }
-            return new ExecutionContext(m_localValues, m_localChangeNotifications, isFlowSuppressed);
+            // Flow suppressing a Default context will have null values, set them to Empty
+            return new ExecutionContext(m_localValues ?? AsyncLocalValueMap.Empty, m_localChangeNotifications ?? Array.Empty<IAsyncLocal>(), isFlowSuppressed);
         }
 
         public static AsyncFlowControl SuppressFlow()
@@ -128,134 +108,248 @@ namespace System.Threading
             return executionContext != null && executionContext.m_isFlowSuppressed;
         }
 
+        internal bool HasChangeNotifications => m_localChangeNotifications != null;
+
+        internal bool IsDefault => m_isDefault;
+
         public static void Run(ExecutionContext executionContext, ContextCallback callback, Object state)
         {
+            // Note: ExecutionContext.Run is an extremely hot function and used by every await, ThreadPool execution, etc.
             if (executionContext == null)
-                throw new InvalidOperationException(SR.InvalidOperation_NullContext);
+            {
+                ThrowNullContext();
+            }
 
-            Thread currentThread = Thread.CurrentThread;
-            ExecutionContextSwitcher ecsw = default(ExecutionContextSwitcher);
+            RunInternal(executionContext, callback, state);
+        }
+
+        internal static void RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
+        {
+            // Note: ExecutionContext.RunInternal is an extremely hot function and used by every await, ThreadPool execution, etc.
+            // Note: Manual enregistering may be addressed by "Exception Handling Write Through Optimization"
+            //       https://github.com/dotnet/coreclr/blob/master/Documentation/design-docs/eh-writethru.md
+
+            // Enregister variables with 0 post-fix so they can be used in registers without EH forcing them to stack
+            // Capture references to Thread Contexts
+            Thread currentThread0 = Thread.CurrentThread;
+            Thread currentThread = currentThread0;
+            ExecutionContext previousExecutionCtx0 = currentThread0.ExecutionContext;
+
+            // Store current ExecutionContext and SynchronizationContext as "previousXxx".
+            // This allows us to restore them and undo any Context changes made in callback.Invoke
+            // so that they won't "leak" back into caller.
+            // These variables will cross EH so be forced to stack
+            ExecutionContext previousExecutionCtx = previousExecutionCtx0;
+            SynchronizationContext previousSyncCtx = currentThread0.SynchronizationContext;
+
+            if (executionContext != null && executionContext.m_isDefault)
+            {
+                // Default is a null ExecutionContext internally
+                executionContext = null;
+            }
+
+            if (previousExecutionCtx0 != executionContext)
+            {
+                // Restore changed ExecutionContext
+                currentThread0.ExecutionContext = executionContext;
+                if ((executionContext != null && executionContext.HasChangeNotifications) ||
+                    (previousExecutionCtx0 != null && previousExecutionCtx0.HasChangeNotifications))
+                {
+                    // There are change notifications; trigger any affected
+                    OnValuesChanged(previousExecutionCtx0, executionContext);
+                }
+            }
+
+            ExceptionDispatchInfo edi = null;
             try
             {
-                EstablishCopyOnWriteScope(currentThread, ref ecsw);
-                ExecutionContext.Restore(currentThread, executionContext);
-                callback(state);
+                callback.Invoke(state);
             }
-            catch
+            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.  That means we need to
-                // end the scope separately in the non-exceptional case below.
-                ecsw.Undo(currentThread);
-                throw;
+                // context before any of our callers' EH filters run.
+                edi = ExceptionDispatchInfo.Capture(ex);
             }
-            ecsw.Undo(currentThread);
-        }
 
-        internal static void Restore(Thread currentThread, ExecutionContext executionContext)
-        {
-            Debug.Assert(currentThread == Thread.CurrentThread);
-
-            ExecutionContext previous = currentThread.ExecutionContext ?? Default;
-            currentThread.ExecutionContext = executionContext;
-
-            // New EC could be null if that's what ECS.Undo saved off.
-            // For the purposes of dealing with context change, treat this as the default EC
-            executionContext = executionContext ?? Default;
-
-            if (previous != executionContext)
+            // Re-enregistrer variables post EH with 1 post-fix so they can be used in registers rather than from stack
+            SynchronizationContext previousSyncCtx1 = previousSyncCtx;
+            Thread currentThread1 = currentThread;
+            // The common case is that these have not changed, so avoid the cost of a write barrier if not needed.
+            if (currentThread1.SynchronizationContext != previousSyncCtx1)
             {
-                OnContextChanged(previous, executionContext);
+                // Restore changed SynchronizationContext back to previous
+                currentThread1.SynchronizationContext = previousSyncCtx1;
             }
-        }
 
-        internal static void EstablishCopyOnWriteScope(Thread currentThread, ref ExecutionContextSwitcher ecsw)
-        {
-            Debug.Assert(currentThread == Thread.CurrentThread);
+            ExecutionContext previousExecutionCtx1 = previousExecutionCtx;
+            ExecutionContext currentExecutionCtx1 = currentThread1.ExecutionContext;
+            if (currentExecutionCtx1 != previousExecutionCtx1)
+            {
+                // Restore changed ExecutionContext back to previous
+                currentThread1.ExecutionContext = previousExecutionCtx1;
+                if ((currentExecutionCtx1 != null && currentExecutionCtx1.HasChangeNotifications) ||
+                    (previousExecutionCtx1 != null && previousExecutionCtx1.HasChangeNotifications))
+                {
+                    // There are change notifications; trigger any affected
+                    OnValuesChanged(currentExecutionCtx1, previousExecutionCtx1);
+                }
+            }
 
-            ecsw.m_ec = currentThread.ExecutionContext;
-            ecsw.m_sc = currentThread.SynchronizationContext;
+            // If exception was thrown by callback, rethrow it now original contexts are restored
+            edi?.Throw();
         }
 
-        private static void OnContextChanged(ExecutionContext previous, ExecutionContext current)
+        internal static void OnValuesChanged(ExecutionContext previousExecutionCtx, ExecutionContext nextExecutionCtx)
         {
-            Debug.Assert(previous != null);
-            Debug.Assert(current != null);
-            Debug.Assert(previous != current);
+            Debug.Assert(previousExecutionCtx != nextExecutionCtx);
 
-            foreach (IAsyncLocal local in previous.m_localChangeNotifications)
-            {
-                object previousValue;
-                object currentValue;
-                previous.m_localValues.TryGetValue(local, out previousValue);
-                current.m_localValues.TryGetValue(local, out currentValue);
+            // Collect Change Notifications 
+            IAsyncLocal[] previousChangeNotifications = previousExecutionCtx?.m_localChangeNotifications;
+            IAsyncLocal[] nextChangeNotifications = nextExecutionCtx?.m_localChangeNotifications;
 
-                if (previousValue != currentValue)
-                    local.OnValueChanged(previousValue, currentValue, true);
-            }
+            // At least one side must have notifications
+            Debug.Assert(previousChangeNotifications != null || nextChangeNotifications != null);
 
-            if (current.m_localChangeNotifications != previous.m_localChangeNotifications)
+            // Fire Change Notifications
+            try
             {
-                try
+                if (previousChangeNotifications != null && nextChangeNotifications != null)
                 {
-                    foreach (IAsyncLocal local in current.m_localChangeNotifications)
+                    // Notifications can't exist without values
+                    Debug.Assert(previousExecutionCtx.m_localValues != null);
+                    Debug.Assert(nextExecutionCtx.m_localValues != null);
+                    // Both contexts have change notifications, check previousExecutionCtx first
+                    foreach (IAsyncLocal local in previousChangeNotifications)
                     {
-                        // If the local has a value in the previous context, we already fired the event for that local
-                        // in the code above.
-                        object previousValue;
-                        if (!previous.m_localValues.TryGetValue(local, out previousValue))
+                        previousExecutionCtx.m_localValues.TryGetValue(local, out object previousValue);
+                        nextExecutionCtx.m_localValues.TryGetValue(local, out object currentValue);
+
+                        if (previousValue != currentValue)
                         {
-                            object currentValue;
-                            current.m_localValues.TryGetValue(local, out currentValue);
+                            local.OnValueChanged(previousValue, currentValue, contextChanged: true);
+                        }
+                    }
 
-                            if (previousValue != currentValue)
-                                local.OnValueChanged(previousValue, currentValue, true);
+                    if (nextChangeNotifications != previousChangeNotifications)
+                    {
+                        // Check for additional notifications in nextExecutionCtx
+                        foreach (IAsyncLocal local in nextChangeNotifications)
+                        {
+                            // If the local has a value in the previous context, we already fired the event 
+                            // for that local in the code above.
+                            if (!previousExecutionCtx.m_localValues.TryGetValue(local, out object previousValue))
+                            {
+                                nextExecutionCtx.m_localValues.TryGetValue(local, out object currentValue);
+                                if (previousValue != currentValue)
+                                {
+                                    local.OnValueChanged(previousValue, currentValue, contextChanged: true);
+                                }
+                            }
                         }
                     }
                 }
-                catch (Exception ex)
+                else if (previousChangeNotifications != null)
                 {
-                    Environment.FailFast(
-                        SR.ExecutionContext_ExceptionInAsyncLocalNotification,
-                        ex);
+                    // Notifications can't exist without values
+                    Debug.Assert(previousExecutionCtx.m_localValues != null);
+                    // No current values, so just check previous against null
+                    foreach (IAsyncLocal local in previousChangeNotifications)
+                    {
+                        previousExecutionCtx.m_localValues.TryGetValue(local, out object previousValue);
+                        if (previousValue != null)
+                        {
+                            local.OnValueChanged(previousValue, null, contextChanged: true);
+                        }
+                    }
                 }
+                else // Implied: nextChangeNotifications != null
+                {
+                    // Notifications can't exist without values
+                    Debug.Assert(nextExecutionCtx.m_localValues != null);
+                    // No previous values, so just check current against null
+                    foreach (IAsyncLocal local in nextChangeNotifications)
+                    {
+                        nextExecutionCtx.m_localValues.TryGetValue(local, out object currentValue);
+                        if (currentValue != null)
+                        {
+                            local.OnValueChanged(null, currentValue, contextChanged: true);
+                        }
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                Environment.FailFast(
+                    SR.ExecutionContext_ExceptionInAsyncLocalNotification,
+                    ex);
             }
         }
 
+        [StackTraceHidden]
+        private static void ThrowNullContext()
+        {
+            throw new InvalidOperationException(SR.InvalidOperation_NullContext);
+        }
+
         internal static object GetLocalValue(IAsyncLocal local)
         {
             ExecutionContext current = Thread.CurrentThread.ExecutionContext;
             if (current == null)
+            {
                 return null;
+            }
 
-            object value;
-            current.m_localValues.TryGetValue(local, out value);
+            current.m_localValues.TryGetValue(local, out object value);
             return value;
         }
 
         internal static void SetLocalValue(IAsyncLocal local, object newValue, bool needChangeNotifications)
         {
-            ExecutionContext current = Thread.CurrentThread.ExecutionContext ?? ExecutionContext.Default;
+            ExecutionContext current = Thread.CurrentThread.ExecutionContext;
 
-            object previousValue;
-            bool hadPreviousValue = current.m_localValues.TryGetValue(local, out previousValue);
+            object previousValue = null;
+            bool hadPreviousValue = false;
+            if (current != null)
+            {
+                hadPreviousValue = current.m_localValues.TryGetValue(local, out previousValue);
+            }
 
             if (previousValue == newValue)
+            {
                 return;
+            }
 
-            IAsyncLocalValueMap newValues = current.m_localValues.Set(local, newValue);
+            IAsyncLocal[] newChangeNotifications = null;
+            IAsyncLocalValueMap newValues;
+            bool isFlowSuppressed = false;
+            if (current != null)
+            {
+                isFlowSuppressed = current.m_isFlowSuppressed;
+                newValues = current.m_localValues.Set(local, newValue);
+                newChangeNotifications = current.m_localChangeNotifications;
+            }
+            else
+            {
+                // First AsyncLocal
+                newValues = new AsyncLocalValueMap.OneElementAsyncLocalValueMap(local, newValue);
+            }
 
             //
             // Either copy the change notification array, or create a new one, depending on whether we need to add a new item.
             //
-            IAsyncLocal[] newChangeNotifications = current.m_localChangeNotifications;
             if (needChangeNotifications)
             {
                 if (hadPreviousValue)
                 {
+                    Debug.Assert(newChangeNotifications != null);
                     Debug.Assert(Array.IndexOf(newChangeNotifications, local) >= 0);
                 }
+                else if (newChangeNotifications == null)
+                {
+                    newChangeNotifications = new IAsyncLocal[1] { local };
+                }
                 else
                 {
                     int newNotificationIndex = newChangeNotifications.Length;
@@ -264,12 +358,14 @@ namespace System.Threading
                 }
             }
 
-            Thread.CurrentThread.ExecutionContext =
-                new ExecutionContext(newValues, newChangeNotifications, current.m_isFlowSuppressed);
+            Thread.CurrentThread.ExecutionContext = 
+                (!isFlowSuppressed && newValues.GetType() == typeof(AsyncLocalValueMap.EmptyAsyncLocalValueMap)) ?
+                null : // No values, return to Default context
+                new ExecutionContext(newValues, newChangeNotifications, isFlowSuppressed);
 
             if (needChangeNotifications)
             {
-                local.OnValueChanged(previousValue, newValue, false);
+                local.OnValueChanged(previousValue, newValue, contextChanged: false);
             }
         }
 
index d9ed08f..63cc0c9 100644 (file)
@@ -673,7 +673,7 @@ namespace System.IO
                     var invokeAsyncCallback = s_invokeAsyncCallback;
                     if (invokeAsyncCallback == null) s_invokeAsyncCallback = invokeAsyncCallback = InvokeAsyncCallback; // benign race condition
 
-                    ExecutionContext.Run(context, invokeAsyncCallback, this);
+                    ExecutionContext.RunInternal(context, invokeAsyncCallback, this);
                 }
             }
 
index 022f15a..de38583 100644 (file)
@@ -49,8 +49,9 @@ namespace System.Runtime.CompilerServices
         /// <param name="stateMachine">The state machine instance, passed by reference.</param>
         /// <exception cref="System.ArgumentNullException">The <paramref name="stateMachine"/> argument was null (Nothing in Visual Basic).</exception>
         [DebuggerStepThrough]
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine =>
-            _builder.Start(ref stateMachine);
+            AsyncMethodBuilder.Start(ref stateMachine);
 
         /// <summary>Associates the builder with the state machine it represents.</summary>
         /// <param name="stateMachine">The heap-allocated state machine object.</param>
@@ -198,8 +199,9 @@ namespace System.Runtime.CompilerServices
         /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
         /// <param name="stateMachine">The state machine instance, passed by reference.</param>
         [DebuggerStepThrough]
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine =>
-            m_builder.Start(ref stateMachine);
+            AsyncMethodBuilder.Start(ref stateMachine);
 
         /// <summary>Associates the builder with the state machine it represents.</summary>
         /// <param name="stateMachine">The heap-allocated state machine object.</param>
@@ -312,29 +314,9 @@ namespace System.Runtime.CompilerServices
         /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
         /// <param name="stateMachine">The state machine instance, passed by reference.</param>
         [DebuggerStepThrough]
-        public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
-        {
-            if (stateMachine == null) // TStateMachines are generally non-nullable value types, so this check will be elided
-            {
-                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.stateMachine);
-            }
-
-            // Run the MoveNext method within a copy-on-write ExecutionContext scope.
-            // This allows us to undo any ExecutionContext changes made in MoveNext,
-            // so that they won't "leak" out of the first await.
-
-            Thread currentThread = Thread.CurrentThread;
-            ExecutionContextSwitcher ecs = default(ExecutionContextSwitcher);
-            try
-            {
-                ExecutionContext.EstablishCopyOnWriteScope(currentThread, ref ecs);
-                stateMachine.MoveNext();
-            }
-            finally
-            {
-                ecs.Undo(currentThread);
-            }
-        }
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine =>
+            AsyncMethodBuilder.Start(ref stateMachine);
 
         /// <summary>Associates the builder with the state machine it represents.</summary>
         /// <param name="stateMachine">The heap-allocated state machine object.</param>
@@ -548,13 +530,14 @@ namespace System.Runtime.CompilerServices
             /// <summary>Calls MoveNext on <see cref="StateMachine"/></summary>
             public void MoveNext()
             {
-                if (Context == null)
+                ExecutionContext context = Context;
+                if (context == null)
                 {
                     StateMachine.MoveNext();
                 }
                 else
                 {
-                    ExecutionContext.Run(Context, s_callback, this);
+                    ExecutionContext.RunInternal(context, s_callback, this);
                 }
 
                 // In case this is a state machine box with a finalizer, suppress its finalization
index 9ace8d3..07b9328 100644 (file)
@@ -924,9 +924,10 @@ namespace System.Threading
 
             public void ExecuteCallback()
             {
-                if (ExecutionContext != null)
+                ExecutionContext context = ExecutionContext;
+                if (context != null)
                 {
-                    ExecutionContext.Run(ExecutionContext, s =>
+                    ExecutionContext.RunInternal(context, s =>
                     {
                         CallbackNode n = (CallbackNode)s;
                         n.Callback(n.CallbackState);
index 44fe29c..660e9f4 100644 (file)
@@ -94,7 +94,7 @@ namespace System.Threading
                 overlapped = OverlappedData.GetOverlappedFromNative(pOVERLAP).m_overlapped;
                 helper = overlapped.iocbHelper;
 
-                if (helper == null || helper._executionContext == null || helper._executionContext == ExecutionContext.Default)
+                if (helper == null || helper._executionContext == null || helper._executionContext.IsDefault)
                 {
                     // We got here because of UnsafePack (or) Pack with EC flow supressed
                     IOCompletionCallback callback = overlapped.UserCallback;
@@ -106,7 +106,7 @@ namespace System.Threading
                     helper._errorCode = errorCode;
                     helper._numBytes = numBytes;
                     helper._pOVERLAP = pOVERLAP;
-                    ExecutionContext.Run(helper._executionContext, _ccb, helper);
+                    ExecutionContext.RunInternal(helper._executionContext, _ccb, helper);
                 }
 
                 //Quickly check the VM again, to see if a packet has arrived.
index 5c9754f..56ae63f 100644 (file)
@@ -2437,7 +2437,7 @@ namespace System.Threading.Tasks
                     else
                     {
                         // Invoke it under the captured ExecutionContext
-                        ExecutionContext.Run(ec, s_ecCallback, this);
+                        ExecutionContext.RunInternal(ec, s_ecCallback, this);
                     }
                 }
                 catch (Exception exn)
index 42fc40e..cb18c66 100644 (file)
@@ -640,14 +640,15 @@ namespace System.Threading.Tasks
                 // 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 (m_capturedContext == null)
+                ExecutionContext context = m_capturedContext;
+                if (context == null)
                 {
                     m_action();
                 }
                 // If there is an execution context, get the cached delegate and run the action under the context.
                 else
                 {
-                    ExecutionContext.Run(m_capturedContext, GetInvokeActionCallback(), m_action);
+                    ExecutionContext.RunInternal(context, GetInvokeActionCallback(), m_action);
                 }
             }
             finally
@@ -708,10 +709,17 @@ namespace System.Threading.Tasks
             {
                 if (prevCurrentTask != null) currentTask = null;
 
-                // If there's no captured context, just run the callback directly.
-                if (m_capturedContext == null) callback(state);
-                // Otherwise, use the captured context to do so.
-                else ExecutionContext.Run(m_capturedContext, callback, state);
+                ExecutionContext context = m_capturedContext;
+                if (context == null)
+                {
+                    // If there's no captured context, just run the callback directly.
+                    callback(state);
+                }
+                else
+                {
+                    // Otherwise, use the captured context to do so.
+                    ExecutionContext.RunInternal(context, callback, state);
+                }
             }
             catch (Exception exc) // we explicitly do not request handling of dangerous exceptions like AVs
             {
index c9ae3d8..aea3616 100644 (file)
@@ -65,9 +65,10 @@ namespace System.Threading
         internal void ThreadStart(object obj)
         {
             _startArg = obj;
-            if (_executionContext != null)
+            ExecutionContext context = _executionContext;
+            if (context != null)
             {
-                ExecutionContext.Run(_executionContext, _ccb, (Object)this);
+                ExecutionContext.RunInternal(context, _ccb, (Object)this);
             }
             else
             {
@@ -78,9 +79,10 @@ namespace System.Threading
         // call back helper
         internal void ThreadStart()
         {
-            if (_executionContext != null)
+            ExecutionContext context = _executionContext;
+            if (context != null)
             {
-                ExecutionContext.Run(_executionContext, _ccb, (Object)this);
+                ExecutionContext.RunInternal(context, _ccb, (Object)this);
             }
             else
             {
index 1b59163..281e5ab 100644 (file)
@@ -896,9 +896,9 @@ namespace System.Threading
 
     internal sealed class QueueUserWorkItemCallback : IThreadPoolWorkItem
     {
-        private WaitCallback callback;
-        private readonly ExecutionContext context;
-        private readonly Object state;
+        private WaitCallback _callback;
+        private readonly ExecutionContext _context;
+        private readonly Object _state;
 
 #if DEBUG
         private volatile int executed;
@@ -921,9 +921,9 @@ namespace System.Threading
 
         internal QueueUserWorkItemCallback(WaitCallback waitCallback, Object stateObj, ExecutionContext ec)
         {
-            callback = waitCallback;
-            state = stateObj;
-            context = ec;
+            _callback = waitCallback;
+            _state = stateObj;
+            _context = ec;
         }
 
         void IThreadPoolWorkItem.ExecuteWorkItem()
@@ -932,15 +932,16 @@ namespace System.Threading
             MarkExecuted(aborted: false);
 #endif
             // call directly if it is an unsafe call OR EC flow is suppressed
+            ExecutionContext context = _context;
             if (context == null)
             {
-                WaitCallback cb = callback;
-                callback = null;
-                cb(state);
+                WaitCallback cb = _callback;
+                _callback = null;
+                cb(_state);
             }
             else
             {
-                ExecutionContext.Run(context, ccb, this);
+                ExecutionContext.RunInternal(context, ccb, this);
             }
         }
 
@@ -958,9 +959,9 @@ namespace System.Threading
         private static void WaitCallback_Context(Object state)
         {
             QueueUserWorkItemCallback obj = (QueueUserWorkItemCallback)state;
-            WaitCallback wc = obj.callback;
+            WaitCallback wc = obj._callback;
             Debug.Assert(null != wc);
-            wc(obj.state);
+            wc(obj._state);
         }
     }
 
@@ -999,7 +1000,8 @@ namespace System.Threading
 #if DEBUG
             MarkExecuted(aborted: false);
 #endif
-            ExecutionContext.Run(ExecutionContext.Default, ccb, this);
+            // null executionContext on RunInternal is Default context
+            ExecutionContext.RunInternal(executionContext: null, ccb, this);
         }
 
         void IThreadPoolWorkItem.MarkAborted(ThreadAbortException tae)
@@ -1061,14 +1063,15 @@ namespace System.Threading
             _ThreadPoolWaitOrTimerCallback helper = (_ThreadPoolWaitOrTimerCallback)state;
             Debug.Assert(helper != null, "Null state passed to PerformWaitOrTimerCallback!");
             // call directly if it is an unsafe call OR EC flow is suppressed
-            if (helper._executionContext == null)
+            ExecutionContext context = helper._executionContext;
+            if (context == null)
             {
                 WaitOrTimerCallback callback = helper._waitOrTimerCallback;
                 callback(helper._state, timedOut);
             }
             else
             {
-                ExecutionContext.Run(helper._executionContext, timedOut ? _ccbt : _ccbf, helper);
+                ExecutionContext.Run(context, timedOut ? _ccbt : _ccbf, helper);
             }
         }
     }
@@ -1284,7 +1287,7 @@ namespace System.Threading
 
             ExecutionContext context = ExecutionContext.Capture();
 
-            IThreadPoolWorkItem tpcallBack = context == ExecutionContext.Default ?
+            IThreadPoolWorkItem tpcallBack = (context != null && context.IsDefault) ?
                 new QueueUserWorkItemCallbackDefaultContext(callBack, state) :
                 (IThreadPoolWorkItem)new QueueUserWorkItemCallback(callBack, state, context);
 
index 991f53f..90dd207 100644 (file)
@@ -600,13 +600,14 @@ namespace System.Threading
                 FrameworkEventSource.Log.ThreadTransferReceiveObj(this, 1, string.Empty);
 
             // call directly if EC flow is suppressed
-            if (m_executionContext == null)
+            ExecutionContext context = m_executionContext;
+            if (context == null)
             {
                 m_timerCallback(m_state);
             }
             else
             {
-                ExecutionContext.Run(m_executionContext, s_callCallbackInContext, this);
+                ExecutionContext.RunInternal(context, s_callCallbackInContext, this);
             }
         }