Use a cached canceled task in ValueTask (dotnet/corefx#27967)
authorStephen Toub <stoub@microsoft.com>
Mon, 12 Mar 2018 19:25:17 +0000 (15:25 -0400)
committerJan Kotas <jkotas@microsoft.com>
Mon, 12 Mar 2018 22:02:50 +0000 (15:02 -0700)
When an IValueTaskSource reports it completed due to cancellation by the time we AsTask it, we can use a cached canceled task if a) this is the netstandard build, or b) this is the netcoreapp build and it reported cancellation but threw an exception other than a cancellation exception.  We cache the non-generic one in a readonly static, since there's only one of them, but lazily cache the generic one.

Signed-off-by: dotnet-bot-corefx-mirror <dotnet-bot@microsoft.com>
src/mscorlib/shared/System/Threading/Tasks/ValueTask.cs

index 8190921..56d5f54 100644 (file)
@@ -25,6 +25,14 @@ namespace System.Threading.Tasks
     [StructLayout(LayoutKind.Auto)]
     public readonly struct ValueTask : IEquatable<ValueTask>
     {
+        /// <summary>A task canceled using `new CancellationToken(true)`.</summary>
+        private static readonly Task s_canceledTask =
+#if netstandard
+            Task.Delay(Timeout.Infinite, new CancellationToken(canceled: true));
+#else
+            Task.FromCanceled(new CancellationToken(canceled: true));
+#endif
+        /// <summary>A successfully completed task.</summary>
         internal static Task CompletedTask
 #if netstandard
             { get; } = Task.Delay(0);
@@ -175,22 +183,15 @@ namespace System.Threading.Tasks
                 {
                     if (status == ValueTaskSourceStatus.Canceled)
                     {
-#if netstandard
-                        var tcs = new TaskCompletionSource<bool>();
-                        tcs.TrySetCanceled();
-                        return tcs.Task;
-#else
+#if !netstandard
                         if (exc is OperationCanceledException oce)
                         {
                             var task = new Task<VoidTaskResult>();
                             task.TrySetCanceled(oce.CancellationToken, oce);
                             return task;
                         }
-                        else
-                        {
-                            return Task.FromCanceled(new CancellationToken(true));
-                        }
 #endif
+                        return s_canceledTask;
                     }
                     else
                     {
@@ -371,6 +372,8 @@ namespace System.Threading.Tasks
     [StructLayout(LayoutKind.Auto)]
     public readonly struct ValueTask<TResult> : IEquatable<ValueTask<TResult>>
     {
+        /// <summary>A task canceled using `new CancellationToken(true)`. Lazily created only when first needed.</summary>
+        private static Task<TResult> s_canceledTask;
         /// <summary>null if <see cref="_result"/> has the result, otherwise a <see cref="Task{TResult}"/> or a <see cref="IValueTaskSource{TResult}"/>.</summary>
         internal readonly object _obj;
         /// <summary>The result to be used if the operation completed successfully synchronously.</summary>
@@ -548,22 +551,29 @@ namespace System.Threading.Tasks
                 {
                     if (status == ValueTaskSourceStatus.Canceled)
                     {
-#if netstandard
-                        var tcs = new TaskCompletionSource<TResult>();
-                        tcs.TrySetCanceled();
-                        return tcs.Task;
-#else
+#if !netstandard
                         if (exc is OperationCanceledException oce)
                         {
                             var task = new Task<TResult>();
                             task.TrySetCanceled(oce.CancellationToken, oce);
                             return task;
                         }
-                        else
+#endif
+
+                        Task<TResult> canceledTask = s_canceledTask;
+                        if (canceledTask == null)
                         {
-                            return Task.FromCanceled<TResult>(new CancellationToken(true));
-                        }
+#if netstandard
+                            var tcs = new TaskCompletionSource<TResult>();
+                            tcs.TrySetCanceled();
+                            canceledTask = tcs.Task;
+#else
+                            canceledTask = Task.FromCanceled<TResult>(new CancellationToken(true));
 #endif
+                            // Benign race condition to initialize cached task, as identity doesn't matter.
+                            s_canceledTask = canceledTask;
+                        }
+                        return canceledTask;
                     }
                     else
                     {