Skip to content

Commit 9bda919

Browse files
authored
Dedup Task.WhenAll non-generic and generic implementations (#88154)
The generic implementation was calling the non-generic one and then using an additional continuation to extract the resulting `Task<TResult>` due to lack of covariance on classes. We can instead just factor the shared implementation out into a generic with the type parameter constrained to be a Task. This results in simpler code as well as avoiding an extra continuation in the generic case. As part of cleaning this up: - I changed code where we need to make a defensive copy of an input collection to use CopyTo; we were already doing this in some places but not others. This saves on an enumerator allocation when enumerating the source collection, as well as multiple interface calls. - I augmented WhenAny to also special-case `List<Task>`, as that's a common input and we can handle it a bit more efficiently, especially if the collection ends up containing just two tasks. - I removed the `GenericDelegateCache<TAntecedentResult, TResult>`. That was from a time before the C# compiler supported caching of generic lambdas. It would have needed to have been updated to handle the stronger type coming out of CommonCWAnyLogic, so I instead just got rid of it. We're better off lazily-creating these rarely used delegates, anyway.
1 parent d29c1c5 commit 9bda919

File tree

3 files changed

+128
-139
lines changed

3 files changed

+128
-139
lines changed

src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/FutureFactory.cs

Lines changed: 13 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1626,15 +1626,19 @@ internal static Task<TResult> ContinueWhenAllImpl<TAntecedentResult>(Task<TAntec
16261626
if (continuationFunction != null)
16271627
{
16281628
return starter.ContinueWith(
1629-
GenericDelegateCache<TAntecedentResult, TResult>.CWAllFuncDelegate,
1629+
static (starter, continuationFunction) => ((Func<Task<TAntecedentResult>[], TResult>)continuationFunction!)(starter.Result),
16301630
continuationFunction, scheduler, cancellationToken, continuationOptions);
16311631
}
16321632
else
16331633
{
16341634
Debug.Assert(continuationAction != null);
16351635

16361636
return starter.ContinueWith(
1637-
GenericDelegateCache<TAntecedentResult, TResult>.CWAllActionDelegate,
1637+
static (starter, continuationAction) =>
1638+
{
1639+
((Action<Task<TAntecedentResult>[]>)continuationAction!)(starter.Result);
1640+
return default(TResult)!;
1641+
},
16381642
continuationAction, scheduler, cancellationToken, continuationOptions);
16391643
}
16401644
}
@@ -2026,7 +2030,7 @@ internal static Task<TResult> ContinueWhenAnyImpl<TAntecedentResult>(Task<TAntec
20262030
if (scheduler == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.scheduler);
20272031

20282032
// Call common ContinueWhenAny setup logic, extract starter
2029-
Task<Task> starter = TaskFactory.CommonCWAnyLogic(tasks);
2033+
Task<Task<TAntecedentResult>> starter = TaskFactory.CommonCWAnyLogic(tasks);
20302034

20312035
// Bail early if cancellation has been requested.
20322036
if (cancellationToken.IsCancellationRequested
@@ -2040,64 +2044,20 @@ internal static Task<TResult> ContinueWhenAnyImpl<TAntecedentResult>(Task<TAntec
20402044
if (continuationFunction != null)
20412045
{
20422046
return starter.ContinueWith(
2043-
GenericDelegateCache<TAntecedentResult, TResult>.CWAnyFuncDelegate,
2047+
static (starter, continuationFunction) => ((Func<Task<TAntecedentResult>, TResult>)continuationFunction!)(starter.Result),
20442048
continuationFunction, scheduler, cancellationToken, continuationOptions);
20452049
}
20462050
else
20472051
{
20482052
Debug.Assert(continuationAction != null);
20492053
return starter.ContinueWith(
2050-
GenericDelegateCache<TAntecedentResult, TResult>.CWAnyActionDelegate,
2054+
static (starter, continuationAction) =>
2055+
{
2056+
((Action<Task<TAntecedentResult>>)continuationAction!)(starter.Result);
2057+
return default(TResult)!;
2058+
},
20512059
continuationAction, scheduler, cancellationToken, continuationOptions);
20522060
}
20532061
}
20542062
}
2055-
2056-
// For the ContinueWhenAnyImpl/ContinueWhenAllImpl methods that are generic on TAntecedentResult,
2057-
// the compiler won't cache the internal ContinueWith delegate because it is generic on both
2058-
// TAntecedentResult and TResult. The GenericDelegateCache serves as a cache for those delegates.
2059-
internal static class GenericDelegateCache<TAntecedentResult, TResult>
2060-
{
2061-
// ContinueWith delegate for TaskFactory<TResult>.ContinueWhenAnyImpl<TAntecedentResult>(non-null continuationFunction)
2062-
internal static Func<Task<Task>, object?, TResult> CWAnyFuncDelegate =
2063-
static (Task<Task> wrappedWinner, object? state) =>
2064-
{
2065-
Debug.Assert(state is Func<Task<TAntecedentResult>, TResult>);
2066-
var func = (Func<Task<TAntecedentResult>, TResult>)state;
2067-
var arg = (Task<TAntecedentResult>)wrappedWinner.Result;
2068-
return func(arg);
2069-
};
2070-
2071-
// ContinueWith delegate for TaskFactory<TResult>.ContinueWhenAnyImpl<TAntecedentResult>(non-null continuationAction)
2072-
internal static Func<Task<Task>, object?, TResult> CWAnyActionDelegate =
2073-
static (Task<Task> wrappedWinner, object? state) =>
2074-
{
2075-
Debug.Assert(state is Action<Task<TAntecedentResult>>);
2076-
var action = (Action<Task<TAntecedentResult>>)state;
2077-
var arg = (Task<TAntecedentResult>)wrappedWinner.Result;
2078-
action(arg);
2079-
return default!;
2080-
};
2081-
2082-
// ContinueWith delegate for TaskFactory<TResult>.ContinueWhenAllImpl<TAntecedentResult>(non-null continuationFunction)
2083-
internal static Func<Task<Task<TAntecedentResult>[]>, object?, TResult> CWAllFuncDelegate =
2084-
static (Task<Task<TAntecedentResult>[]> wrappedAntecedents, object? state) =>
2085-
{
2086-
wrappedAntecedents.NotifyDebuggerOfWaitCompletionIfNecessary();
2087-
Debug.Assert(state is Func<Task<TAntecedentResult>[], TResult>);
2088-
var func = (Func<Task<TAntecedentResult>[], TResult>)state;
2089-
return func(wrappedAntecedents.Result);
2090-
};
2091-
2092-
// ContinueWith delegate for TaskFactory<TResult>.ContinueWhenAllImpl<TAntecedentResult>(non-null continuationAction)
2093-
internal static Func<Task<Task<TAntecedentResult>[]>, object?, TResult> CWAllActionDelegate =
2094-
static (Task<Task<TAntecedentResult>[]> wrappedAntecedents, object? state) =>
2095-
{
2096-
wrappedAntecedents.NotifyDebuggerOfWaitCompletionIfNecessary();
2097-
Debug.Assert(state is Action<Task<TAntecedentResult>[]>);
2098-
var action = (Action<Task<TAntecedentResult>[]>)state;
2099-
action(wrappedAntecedents.Result);
2100-
return default!;
2101-
};
2102-
}
21032063
}

0 commit comments

Comments
 (0)