When we unified the implementations of these across all of our runtimes, we lost all size optimizations people have been doing on the Project N side over the past six years.
This restores a bit of the loss. For one sample app with lots of async usage, this removes 2.1 MB of generic instantiations.
There is more we can do, but I can't spend time on that right now. These two things jumped out on me when I was looking at it back in December and were an easy fix I wanted to do for a while.
Signed-off-by: dotnet-bot <dotnet-bot@microsoft.com>
Commit migrated from https://github.com/dotnet/coreclr/commit/
a19ccc68afa0174a0cfbd58fa241a7f85785f567
where TStateMachine : IAsyncStateMachine
{
/// <summary>Delegate used to invoke on an ExecutionContext when passed an instance of this box type.</summary>
- private static readonly ContextCallback s_callback = s =>
+ private static readonly ContextCallback s_callback = ExecutionContextCallback;
+
+ // Used to initialize s_callback above. We don't use a lambda for this on purpose: a lambda would
+ // introduce a new generic type behind the scenes that comes with a hefty size penalty in AOT builds.
+ private static void ExecutionContextCallback(object s)
{
Debug.Assert(s is AsyncStateMachineBox<TStateMachine>);
// Only used privately to pass directly to EC.Run
Unsafe.As<AsyncStateMachineBox<TStateMachine>>(s).StateMachine.MoveNext();
- };
+ }
/// <summary>A delegate to the <see cref="MoveNext()"/> method.</summary>
private Action _moveNextAction;
private static readonly TaskFactory<TResult> s_Factory = new TaskFactory<TResult>();
- // Delegate used by:
- // public static Task<Task<TResult>> WhenAny<TResult>(IEnumerable<Task<TResult>> tasks);
- // public static Task<Task<TResult>> WhenAny<TResult>(params Task<TResult>[] tasks);
- // Used to "cast" from Task<Task> to Task<Task<TResult>>.
- internal static readonly Func<Task<Task>, Task<TResult>> TaskWhenAnyCast = completed => (Task<TResult>)completed.Result;
+ // Extract rarely used helper for a static method in a separate type so that the Func<Task<Task>, Task<TResult>>
+ // generic instantiations don't contribute to all Task instantiations, but only those where WhenAny is used.
+ internal static class TaskWhenAnyCast
+ {
+ // Delegate used by:
+ // public static Task<Task<TResult>> WhenAny<TResult>(IEnumerable<Task<TResult>> tasks);
+ // public static Task<Task<TResult>> WhenAny<TResult>(params Task<TResult>[] tasks);
+ // Used to "cast" from Task<Task> to Task<Task<TResult>>.
+ internal static readonly Func<Task<Task>, Task<TResult>> Value = completed => (Task<TResult>)completed.Result;
+ }
// Construct a promise-style task without any options.
internal Task() :
Task<Task> intermediate = WhenAny((Task[])tasks);
// Return a continuation task with the correct result type
- return intermediate.ContinueWith(Task<TResult>.TaskWhenAnyCast, default,
+ return intermediate.ContinueWith(Task<TResult>.TaskWhenAnyCast.Value, default,
TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default);
}
Task<Task> intermediate = WhenAny((IEnumerable<Task>)tasks);
// Return a continuation task with the correct result type
- return intermediate.ContinueWith(Task<TResult>.TaskWhenAnyCast, default,
+ return intermediate.ContinueWith(Task<TResult>.TaskWhenAnyCast.Value, default,
TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default);
}
#endregion