Allow some ITaskCompletionActions to always run synchronously
authorstephentoub <stoub@microsoft.com>
Mon, 18 Jan 2016 12:26:14 +0000 (07:26 -0500)
committerstephentoub <stoub@microsoft.com>
Mon, 18 Jan 2016 12:26:14 +0000 (07:26 -0500)
A few internal Task completion actions do a known and small quantity of work, without any potential to run arbitrary code, e.g. a completion action used in Task.Wait to set a ManualResetEventSlim.  Such actions used to typically run synchronously, but after https://github.com/dotnet/coreclr/pull/2026 can be forced to run asynchronously.  These few actions are considered purely internal implementation details of TPL, need not be subject to forcing continuations to run asynchronously, and have perf benefits to always being run synchronously, e.g. calling Wait on a RunContinuationsAsynchronously task shouldn't require a work item to be queued to unblock the task.

This commit adds a property to ITaskCompletionAction that we can check to determine whether it's ok to force the continuation to run synchronously even if the system says we should be running all continuations asynchronously.  Only those actions which are safe are annotated as such.  We only invoke this interface property getter in cases where we're about to fall back to allocating and queueing a work item, so the cost of the extra interface call is acceptable for the benefits it provides.  As all of this is internal, it can also be tweaked further in the future.

src/mscorlib/src/System/IO/Stream.cs
src/mscorlib/src/System/Threading/Tasks/Task.cs
src/mscorlib/src/System/Threading/Tasks/TaskFactory.cs

index 64721cd..a39e438 100644 (file)
@@ -685,6 +685,8 @@ namespace System.IO {
                     using(context) ExecutionContext.Run(context, invokeAsyncCallback, this, true);
                 }
             }
+
+            bool ITaskCompletionAction.InvokeMayRunArbitraryCode { get { return true; } }
         }
 #endif
 
index a1da093..ca1c03c 100644 (file)
@@ -3283,6 +3283,7 @@ namespace System.Threading.Tasks
         {
             internal SetOnInvokeMres() : base(false, 0) { }
             public void Invoke(Task completingTask) { Set(); }
+            public bool InvokeMayRunArbitraryCode { get { return false; } }
         }
 
         /// <summary>
@@ -3610,7 +3611,7 @@ namespace System.Threading.Tasks
                 ITaskCompletionAction singleTaskCompletionAction = continuationObject as ITaskCompletionAction;
                 if (singleTaskCompletionAction != null)
                 {
-                    if (bCanInlineContinuations)
+                    if (bCanInlineContinuations || !singleTaskCompletionAction.InvokeMayRunArbitraryCode)
                     {
                         singleTaskCompletionAction.Invoke(this);
                     }
@@ -3695,7 +3696,7 @@ namespace System.Threading.Tasks
                             Contract.Assert(currentContinuation is ITaskCompletionAction, "Expected continuation element to be Action, TaskContinuation, or ITaskContinuationAction");
                             var action = (ITaskCompletionAction)currentContinuation;
 
-                            if (bCanInlineContinuations)
+                            if (bCanInlineContinuations || !action.InvokeMayRunArbitraryCode)
                             {
                                 action.Invoke(this);
                             }
@@ -5223,6 +5224,8 @@ namespace System.Threading.Tasks
                 if (Interlocked.Decrement(ref _count) == 0) Set();
                 Contract.Assert(_count >= 0, "Count should never go below 0");
             }
+
+            public bool InvokeMayRunArbitraryCode { get { return false; } }
         }
 
         /// <summary>
@@ -6172,6 +6175,8 @@ namespace System.Threading.Tasks
                 Contract.Assert(m_count >= 0, "Count should never go below 0");
             }
 
+            public bool InvokeMayRunArbitraryCode { get { return true; } }
+
             /// <summary>
             /// Returns whether we should notify the debugger of a wait completion.  This returns 
             /// true iff at least one constituent task has its bit set.
@@ -6422,6 +6427,8 @@ namespace System.Threading.Tasks
                 Contract.Assert(m_count >= 0, "Count should never go below 0");
             }
 
+            public bool InvokeMayRunArbitraryCode { get { return true; } }
+
             /// <summary>
             /// Returns whether we should notify the debugger of a wait completion.  This returns true
             /// iff at least one constituent task has its bit set.
@@ -7111,7 +7118,18 @@ namespace System.Threading.Tasks
     // TaskFactory.CompleteOnCountdownPromise<T>, and TaskFactory.CompleteOnInvokePromise.
     internal interface ITaskCompletionAction
     {
+        /// <summary>Invoked to run the completion action.</summary>
         void Invoke(Task completingTask);
+
+        /// <summary>
+        /// Some completion actions are considered internal implementation details of tasks,
+        /// using the continuation mechanism only for performance reasons.  Such actions perform
+        /// known quantities and types of work, and can be invoked safely as a continuation even
+        /// if the system wants to prevent arbitrary continuations from running synchronously.
+        /// This should only return false for a limited set of implementations where a small amount
+        /// of work is guaranteed to be performed, e.g. setting a ManualResetEventSlim.
+        /// </summary>
+        bool InvokeMayRunArbitraryCode { get; }
     }
 
     // This class encapsulates all "unwrap" logic, and also implements ITaskCompletionAction,
@@ -7326,7 +7344,7 @@ namespace System.Threading.Tasks
             }
         }
 
+        public bool InvokeMayRunArbitraryCode { get { return true; } }
     }
 
-
 }
index 72bdc8b..47d52d2 100644 (file)
@@ -1668,6 +1668,8 @@ namespace System.Threading.Tasks
                 Contract.Assert(_count >= 0, "Count should never go below 0");
             }
 
+            public bool InvokeMayRunArbitraryCode { get { return true; } }
+
             /// <summary>
             /// Returns whether we should notify the debugger of a wait completion.  This returns 
             /// true iff at least one constituent task has its bit set.
@@ -1746,6 +1748,8 @@ namespace System.Threading.Tasks
                 Contract.Assert(_count >= 0, "Count should never go below 0");
             }
 
+            public bool InvokeMayRunArbitraryCode { get { return true; } }
+
             /// <summary>
             /// Returns whether we should notify the debugger of a wait completion.  This returns 
             /// true iff at least one constituent task has its bit set.
@@ -2462,6 +2466,8 @@ namespace System.Threading.Tasks
 
                 }
             }
+
+            public bool InvokeMayRunArbitraryCode { get { return true; } }
         }
         // Common ContinueWhenAny logic
         // If the tasks list is not an array, it must be an internal defensive copy so that