Task.FromResult may return singleton

Task.FromResult<TResult>(TResult) may now return a cached Task<TResult> instance rather than always creating a new instance.

Old behavior

In previous versions, Task.FromResult<TResult>(TResult) would always allocate a new Task<TResult>, regardless of the type of T or the result value.

New behavior

For some T types and some result values, Task.FromResult<TResult>(TResult) may return a cached singleton object rather than allocating a new object. For example, it is likely that every call to Task.FromResult(true) will return the same already-completed Task<bool> object.

Version introduced

.NET 6

Type of breaking change

This change can affect binary compatibility.

Reason for change

Many developers expected Task.FromResult<TResult>(TResult) to behave similarly to asynchronous methods, which already performed such caching. Developers that knew about the allocation behavior often maintained their own cache to avoid the performance cost of always allocating for these commonly used values. For example:

private static readonly Task<bool> s_trueTask = Task.FromResult(true);

Now, such custom caches are no longer required for values such as Boolean and small Int32 values.

Unless you're using reference equality to check whether one Task instance is the same as another Task instance, you should not be impacted by this change. If you are using such reference equality and need to continue this checking, use the following code to be guaranteed to always get back a unique Task<TResult> instance:

private static Task<T> NewInstanceFromResult<T>(T result)
{
    var tcs = new TaskCompletionSource<T>();
    tcs.TrySetResult(result);
    return tcs.Task;
}

Note

This pattern is much less efficient than just using Task.FromResult(result), and should be avoided unless you really need it.

Affected APIs