diff options
Diffstat (limited to 'src/System.Private.CoreLib/shared/System/Threading/Tasks/ValueTask.cs')
-rw-r--r-- | src/System.Private.CoreLib/shared/System/Threading/Tasks/ValueTask.cs | 510 |
1 files changed, 310 insertions, 200 deletions
diff --git a/src/System.Private.CoreLib/shared/System/Threading/Tasks/ValueTask.cs b/src/System.Private.CoreLib/shared/System/Threading/Tasks/ValueTask.cs index 18e4a27a4..4d6a75960 100644 --- a/src/System.Private.CoreLib/shared/System/Threading/Tasks/ValueTask.cs +++ b/src/System.Private.CoreLib/shared/System/Threading/Tasks/ValueTask.cs @@ -14,17 +14,59 @@ using Internal.Runtime.CompilerServices; namespace System.Threading.Tasks { + // TYPE SAFETY WARNING: + // This code uses Unsafe.As to cast _obj. This is done in order to minimize the costs associated with + // casting _obj to a variety of different types that can be stored in a ValueTask, e.g. Task<TResult> + // vs IValueTaskSource<TResult>. Previous attempts at this were faulty due to using a separate field + // to store information about the type of the object in _obj; this is faulty because if the ValueTask + // is stored into a field, concurrent read/writes can result in tearing the _obj from the type information + // stored in a separate field. This means we can rely only on the _obj field to determine how to handle + // it. As such, the pattern employed is to copy _obj into a local obj, and then check it for null and + // type test against Task/Task<TResult>. Since the ValueTask can only be constructed with null, Task, + // or IValueTaskSource, we can then be confident in knowing that if it doesn't match one of those values, + // it must be an IValueTaskSource, and we can use Unsafe.As. This could be defeated by other unsafe means, + // like private reflection or using Unsafe.As manually, but at that point you're already doing things + // that can violate type safety; we only care about getting correct behaviors when using "safe" code. + // There are still other race conditions in user's code that can result in errors, but such errors don't + // cause ValueTask to violate type safety. + /// <summary>Provides an awaitable result of an asynchronous operation.</summary> /// <remarks> - /// <see cref="ValueTask"/>s are meant to be directly awaited. To do more complicated operations with them, a <see cref="Task"/> - /// should be extracted using <see cref="AsTask"/>. Such operations might include caching an instance to be awaited later, - /// registering multiple continuations with a single operation, awaiting the same task multiple times, and using combinators over - /// multiple operations. + /// <see cref="ValueTask"/> instances are meant to be directly awaited. To do more complicated operations with them, a <see cref="Task"/> + /// should be extracted using <see cref="AsTask"/>. Such operations might include caching a task instance to be awaited later, + /// registering multiple continuations with a single task, awaiting the same task multiple times, and using combinators over + /// multiple operations: + /// <list type="bullet"> + /// <item> + /// Once the result of a <see cref="ValueTask"/> instance has been retrieved, do not attempt to retrieve it again. + /// <see cref="ValueTask"/> instances may be backed by <see cref="IValueTaskSource"/> instances that are reusable, and such + /// instances may use the act of retrieving the instances result as a notification that the instance may now be reused for + /// a different operation. Attempting to then reuse that same <see cref="ValueTask"/> results in undefined behavior. + /// </item> + /// <item> + /// Do not attempt to add multiple continuations to the same <see cref="ValueTask"/>. While this might work if the + /// <see cref="ValueTask"/> wraps a <code>T</code> or a <see cref="Task"/>, it may not work if the <see cref="ValueTask"/> + /// was constructed from an <see cref="IValueTaskSource"/>. + /// </item> + /// <item> + /// Some operations that return a <see cref="ValueTask"/> may invalidate it based on some subsequent operation being performed. + /// Unless otherwise documented, assume that a <see cref="ValueTask"/> should be awaited prior to performing any additional operations + /// on the instance from which it was retrieved. + /// </item> + /// </list> /// </remarks> [AsyncMethodBuilder(typeof(AsyncValueTaskMethodBuilder))] [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); @@ -34,10 +76,11 @@ namespace System.Threading.Tasks /// <summary>null if representing a successful synchronous completion, otherwise a <see cref="Task"/> or a <see cref="IValueTaskSource"/>.</summary> internal readonly object _obj; - /// <summary>Flags providing additional details about the ValueTask's contents and behavior.</summary> - internal readonly ValueTaskFlags _flags; /// <summary>Opaque value passed through to the <see cref="IValueTaskSource"/>.</summary> internal readonly short _token; + /// <summary>true to continue on the capture context; otherwise, true.</summary> + /// <remarks>Stored in the <see cref="ValueTask"/> rather than in the configured awaiter to utilize otherwise padding space.</remarks> + internal readonly bool _continueOnCapturedContext; // An instance created with the default ctor (a zero init'd struct) represents a synchronously, successfully completed operation. @@ -53,7 +96,7 @@ namespace System.Threading.Tasks _obj = task; - _flags = ValueTaskFlags.ObjectIsTask; + _continueOnCapturedContext = true; _token = 0; } @@ -71,51 +114,15 @@ namespace System.Threading.Tasks _obj = source; _token = token; - _flags = 0; + _continueOnCapturedContext = true; } - /// <summary>Non-verified initialization of the struct to the specified values.</summary> - /// <param name="obj">The object.</param> - /// <param name="token">The token.</param> - /// <param name="flags">The flags.</param> [MethodImpl(MethodImplOptions.AggressiveInlining)] - private ValueTask(object obj, short token, ValueTaskFlags flags) + private ValueTask(object obj, short token, bool continueOnCapturedContext) { _obj = obj; _token = token; - _flags = flags; - } - - /// <summary>Gets whether the contination should be scheduled to the current context.</summary> - internal bool ContinueOnCapturedContext - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => (_flags & ValueTaskFlags.AvoidCapturedContext) == 0; - } - - /// <summary>Gets whether the object in the <see cref="_obj"/> field is a <see cref="Task"/>.</summary> - internal bool ObjectIsTask - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => (_flags & ValueTaskFlags.ObjectIsTask) != 0; - } - - /// <summary>Returns the <see cref="Task"/> stored in <see cref="_obj"/>. This uses <see cref="Unsafe"/>.</summary> - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal Task UnsafeGetTask() - { - Debug.Assert(ObjectIsTask); - Debug.Assert(_obj is Task); - return Unsafe.As<Task>(_obj); - } - - /// <summary>Returns the <see cref="IValueTaskSource"/> stored in <see cref="_obj"/>. This uses <see cref="Unsafe"/>.</summary> - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal IValueTaskSource UnsafeGetValueTaskSource() - { - Debug.Assert(!ObjectIsTask); - Debug.Assert(_obj is IValueTaskSource); - return Unsafe.As<IValueTaskSource>(_obj); + _continueOnCapturedContext = continueOnCapturedContext; } /// <summary>Returns the hash code for this instance.</summary> @@ -144,18 +151,26 @@ namespace System.Threading.Tasks /// It will either return the wrapped task object if one exists, or it'll /// manufacture a new task object to represent the result. /// </remarks> - public Task AsTask() => - _obj == null ? ValueTask.CompletedTask : - ObjectIsTask ? UnsafeGetTask() : - GetTaskForValueTaskSource(); + public Task AsTask() + { + object obj = _obj; + Debug.Assert(obj == null || obj is Task || obj is IValueTaskSource); + return + obj == null ? CompletedTask : + obj as Task ?? + GetTaskForValueTaskSource(Unsafe.As<IValueTaskSource>(obj)); + } /// <summary>Gets a <see cref="ValueTask"/> that may be used at any point in the future.</summary> public ValueTask Preserve() => _obj == null ? this : new ValueTask(AsTask()); /// <summary>Creates a <see cref="Task"/> to represent the <see cref="IValueTaskSource"/>.</summary> - private Task GetTaskForValueTaskSource() + /// <remarks> + /// The <see cref="IValueTaskSource"/> is passed in rather than reading and casting <see cref="_obj"/> + /// so that the caller can pass in an object it's already validated. + /// </remarks> + private Task GetTaskForValueTaskSource(IValueTaskSource t) { - IValueTaskSource t = UnsafeGetValueTaskSource(); ValueTaskSourceStatus status = t.GetStatus(_token); if (status != ValueTaskSourceStatus.Pending) { @@ -164,7 +179,7 @@ namespace System.Threading.Tasks // Propagate any exceptions that may have occurred, then return // an already successfully completed task. t.GetResult(_token); - return ValueTask.CompletedTask; + return CompletedTask; // If status is Faulted or Canceled, GetResult should throw. But // we can't guarantee every implementation will do the "right thing". @@ -175,22 +190,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 { @@ -205,7 +213,7 @@ namespace System.Threading.Tasks } } - var m = new ValueTaskSourceTask(t, _token); + var m = new ValueTaskSourceAsTask(t, _token); return #if netstandard m.Task; @@ -215,7 +223,7 @@ namespace System.Threading.Tasks } /// <summary>Type used to create a <see cref="Task"/> to represent a <see cref="IValueTaskSource"/>.</summary> - private sealed class ValueTaskSourceTask : + private sealed class ValueTaskSourceAsTask : #if netstandard TaskCompletionSource<bool> #else @@ -224,7 +232,7 @@ namespace System.Threading.Tasks { private static readonly Action<object> s_completionAction = state => { - if (!(state is ValueTaskSourceTask vtst) || + if (!(state is ValueTaskSourceAsTask vtst) || !(vtst._source is IValueTaskSource source)) { // This could only happen if the IValueTaskSource passed the wrong state @@ -270,7 +278,7 @@ namespace System.Threading.Tasks /// <summary>The token to pass through to operations on <see cref="_source"/></summary> private readonly short _token; - public ValueTaskSourceTask(IValueTaskSource source, short token) + public ValueTaskSourceAsTask(IValueTaskSource source, short token) { _token = token; _source = source; @@ -282,30 +290,73 @@ namespace System.Threading.Tasks public bool IsCompleted { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _obj == null || (ObjectIsTask ? UnsafeGetTask().IsCompleted : UnsafeGetValueTaskSource().GetStatus(_token) != ValueTaskSourceStatus.Pending); + get + { + object obj = _obj; + Debug.Assert(obj == null || obj is Task || obj is IValueTaskSource); + + if (obj == null) + { + return true; + } + + if (obj is Task t) + { + return t.IsCompleted; + } + + return Unsafe.As<IValueTaskSource>(obj).GetStatus(_token) != ValueTaskSourceStatus.Pending; + } } /// <summary>Gets whether the <see cref="ValueTask"/> represents a successfully completed operation.</summary> public bool IsCompletedSuccessfully { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => - _obj == null || - (ObjectIsTask ? + get + { + object obj = _obj; + Debug.Assert(obj == null || obj is Task || obj is IValueTaskSource); + + if (obj == null) + { + return true; + } + + if (obj is Task t) + { + return #if netstandard - UnsafeTask.Status == TaskStatus.RanToCompletion : + t.Status == TaskStatus.RanToCompletion; #else - UnsafeGetTask().IsCompletedSuccessfully : + t.IsCompletedSuccessfully; #endif - UnsafeGetValueTaskSource().GetStatus(_token) == ValueTaskSourceStatus.Succeeded); + } + + return Unsafe.As<IValueTaskSource>(obj).GetStatus(_token) == ValueTaskSourceStatus.Succeeded; + } } /// <summary>Gets whether the <see cref="ValueTask"/> represents a failed operation.</summary> public bool IsFaulted { - get => - _obj != null && - (ObjectIsTask ? UnsafeGetTask().IsFaulted : UnsafeGetValueTaskSource().GetStatus(_token) == ValueTaskSourceStatus.Faulted); + get + { + object obj = _obj; + Debug.Assert(obj == null || obj is Task || obj is IValueTaskSource); + + if (obj == null) + { + return false; + } + + if (obj is Task t) + { + return t.IsFaulted; + } + + return Unsafe.As<IValueTaskSource>(obj).GetStatus(_token) == ValueTaskSourceStatus.Faulted; + } } /// <summary>Gets whether the <see cref="ValueTask"/> represents a canceled operation.</summary> @@ -316,9 +367,23 @@ namespace System.Threading.Tasks /// </remarks> public bool IsCanceled { - get => - _obj != null && - (ObjectIsTask ? UnsafeGetTask().IsCanceled : UnsafeGetValueTaskSource().GetStatus(_token) == ValueTaskSourceStatus.Canceled); + get + { + object obj = _obj; + Debug.Assert(obj == null || obj is Task || obj is IValueTaskSource); + + if (obj == null) + { + return false; + } + + if (obj is Task t) + { + return t.IsCanceled; + } + + return Unsafe.As<IValueTaskSource>(obj).GetStatus(_token) == ValueTaskSourceStatus.Canceled; + } } /// <summary>Throws the exception that caused the <see cref="ValueTask"/> to fail. If it completed successfully, nothing is thrown.</summary> @@ -326,19 +391,22 @@ namespace System.Threading.Tasks [StackTraceHidden] internal void ThrowIfCompletedUnsuccessfully() { - if (_obj != null) + object obj = _obj; + Debug.Assert(obj == null || obj is Task || obj is IValueTaskSource); + + if (obj != null) { - if (ObjectIsTask) + if (obj is Task t) { #if netstandard - UnsafeTask.GetAwaiter().GetResult(); + t.GetAwaiter().GetResult(); #else - TaskAwaiter.ValidateEnd(UnsafeGetTask()); + TaskAwaiter.ValidateEnd(t); #endif } else { - UnsafeGetValueTaskSource().GetResult(_token); + Unsafe.As<IValueTaskSource>(obj).GetResult(_token); } } } @@ -351,34 +419,51 @@ namespace System.Threading.Tasks /// true to attempt to marshal the continuation back to the captured context; otherwise, false. /// </param> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ConfiguredValueTaskAwaitable ConfigureAwait(bool continueOnCapturedContext) - { - // TODO: Simplify once https://github.com/dotnet/coreclr/pull/16138 is fixed. - bool avoidCapture = !continueOnCapturedContext; - return new ConfiguredValueTaskAwaitable(new ValueTask(_obj, _token, _flags | Unsafe.As<bool, ValueTaskFlags>(ref avoidCapture))); - } + public ConfiguredValueTaskAwaitable ConfigureAwait(bool continueOnCapturedContext) => + new ConfiguredValueTaskAwaitable(new ValueTask(_obj, _token, continueOnCapturedContext)); } /// <summary>Provides a value type that can represent a synchronously available value or a task object.</summary> /// <typeparam name="TResult">Specifies the type of the result.</typeparam> /// <remarks> - /// <see cref="ValueTask{TResult}"/>s are meant to be directly awaited. To do more complicated operations with them, a <see cref="Task"/> - /// should be extracted using <see cref="AsTask"/> or <see cref="Preserve"/>. Such operations might include caching an instance to - /// be awaited later, registering multiple continuations with a single operation, awaiting the same task multiple times, and using - /// combinators over multiple operations. + /// <see cref="ValueTask{TResult}"/> instances are meant to be directly awaited. To do more complicated operations with them, a <see cref="Task{TResult}"/> + /// should be extracted using <see cref="AsTask"/>. Such operations might include caching a task instance to be awaited later, + /// registering multiple continuations with a single task, awaiting the same task multiple times, and using combinators over + /// multiple operations: + /// <list type="bullet"> + /// <item> + /// Once the result of a <see cref="ValueTask{TResult}"/> instance has been retrieved, do not attempt to retrieve it again. + /// <see cref="ValueTask{TResult}"/> instances may be backed by <see cref="IValueTaskSource{TResult}"/> instances that are reusable, and such + /// instances may use the act of retrieving the instances result as a notification that the instance may now be reused for + /// a different operation. Attempting to then reuse that same <see cref="ValueTask{TResult}"/> results in undefined behavior. + /// </item> + /// <item> + /// Do not attempt to add multiple continuations to the same <see cref="ValueTask{TResult}"/>. While this might work if the + /// <see cref="ValueTask{TResult}"/> wraps a <code>T</code> or a <see cref="Task{TResult}"/>, it may not work if the <see cref="Task{TResult}"/> + /// was constructed from an <see cref="IValueTaskSource{TResult}"/>. + /// </item> + /// <item> + /// Some operations that return a <see cref="ValueTask{TResult}"/> may invalidate it based on some subsequent operation being performed. + /// Unless otherwise documented, assume that a <see cref="ValueTask{TResult}"/> should be awaited prior to performing any additional operations + /// on the instance from which it was retrieved. + /// </item> + /// </list> /// </remarks> [AsyncMethodBuilder(typeof(AsyncValueTaskMethodBuilder<>))] [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> internal readonly TResult _result; - /// <summary>Flags providing additional details about the ValueTask's contents and behavior.</summary> - internal readonly ValueTaskFlags _flags; /// <summary>Opaque value passed through to the <see cref="IValueTaskSource{TResult}"/>.</summary> internal readonly short _token; + /// <summary>true to continue on the captured context; otherwise, false.</summary> + /// <remarks>Stored in the <see cref="ValueTask{TResult}"/> rather than in the configured awaiter to utilize otherwise padding space.</remarks> + internal readonly bool _continueOnCapturedContext; // An instance created with the default ctor (a zero init'd struct) represents a synchronously, successfully completed operation // with a result of default(TResult). @@ -391,7 +476,7 @@ namespace System.Threading.Tasks _result = result; _obj = null; - _flags = 0; + _continueOnCapturedContext = true; _token = 0; } @@ -408,7 +493,7 @@ namespace System.Threading.Tasks _obj = task; _result = default; - _flags = ValueTaskFlags.ObjectIsTask; + _continueOnCapturedContext = true; _token = 0; } @@ -427,54 +512,23 @@ namespace System.Threading.Tasks _token = token; _result = default; - _flags = 0; + _continueOnCapturedContext = true; } /// <summary>Non-verified initialization of the struct to the specified values.</summary> /// <param name="obj">The object.</param> /// <param name="result">The result.</param> /// <param name="token">The token.</param> - /// <param name="flags">The flags.</param> + /// <param name="continueOnCapturedContext">true to continue on captured context; otherwise, false.</param> [MethodImpl(MethodImplOptions.AggressiveInlining)] - private ValueTask(object obj, TResult result, short token, ValueTaskFlags flags) + private ValueTask(object obj, TResult result, short token, bool continueOnCapturedContext) { _obj = obj; _result = result; _token = token; - _flags = flags; - } - - /// <summary>Gets whether the contination should be scheduled to the current context.</summary> - internal bool ContinueOnCapturedContext - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => (_flags & ValueTaskFlags.AvoidCapturedContext) == 0; - } - - /// <summary>Gets whether the object in the <see cref="_obj"/> field is a <see cref="Task{TResult}"/>.</summary> - internal bool ObjectIsTask - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => (_flags & ValueTaskFlags.ObjectIsTask) != 0; + _continueOnCapturedContext = continueOnCapturedContext; } - /// <summary>Returns the <see cref="Task{TResult}"/> stored in <see cref="_obj"/>. This uses <see cref="Unsafe"/>.</summary> - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal Task<TResult> UnsafeGetTask() - { - Debug.Assert(ObjectIsTask); - Debug.Assert(_obj is Task<TResult>); - return Unsafe.As<Task<TResult>>(_obj); - } - - /// <summary>Returns the <see cref="IValueTaskSource{TResult}"/> stored in <see cref="_obj"/>. This uses <see cref="Unsafe"/>.</summary> - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal IValueTaskSource<TResult> UnsafeGetValueTaskSource() - { - Debug.Assert(!ObjectIsTask); - Debug.Assert(_obj is IValueTaskSource<TResult>); - return Unsafe.As<IValueTaskSource<TResult>>(_obj); - } /// <summary>Returns the hash code for this instance.</summary> public override int GetHashCode() => @@ -508,23 +562,39 @@ namespace System.Threading.Tasks /// It will either return the wrapped task object if one exists, or it'll /// manufacture a new task object to represent the result. /// </remarks> - public Task<TResult> AsTask() => - _obj == null ? + public Task<TResult> AsTask() + { + object obj = _obj; + Debug.Assert(obj == null || obj is Task<TResult> || obj is IValueTaskSource<TResult>); + + if (obj == null) + { + return #if netstandard - Task.FromResult(_result) : + Task.FromResult(_result); #else - AsyncTaskMethodBuilder<TResult>.GetTaskForResult(_result) : + AsyncTaskMethodBuilder<TResult>.GetTaskForResult(_result); #endif - ObjectIsTask ? UnsafeGetTask() : - GetTaskForValueTaskSource(); + } + + if (obj is Task<TResult> t) + { + return t; + } + + return GetTaskForValueTaskSource(Unsafe.As<IValueTaskSource<TResult>>(obj)); + } /// <summary>Gets a <see cref="ValueTask{TResult}"/> that may be used at any point in the future.</summary> public ValueTask<TResult> Preserve() => _obj == null ? this : new ValueTask<TResult>(AsTask()); /// <summary>Creates a <see cref="Task{TResult}"/> to represent the <see cref="IValueTaskSource{TResult}"/>.</summary> - private Task<TResult> GetTaskForValueTaskSource() + /// <remarks> + /// The <see cref="IValueTaskSource{TResult}"/> is passed in rather than reading and casting <see cref="_obj"/> + /// so that the caller can pass in an object it's already validated. + /// </remarks> + private Task<TResult> GetTaskForValueTaskSource(IValueTaskSource<TResult> t) { - IValueTaskSource<TResult> t = UnsafeGetValueTaskSource(); ValueTaskSourceStatus status = t.GetStatus(_token); if (status != ValueTaskSourceStatus.Pending) { @@ -548,22 +618,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 { @@ -578,7 +655,7 @@ namespace System.Threading.Tasks } } - var m = new ValueTaskSourceTask(t, _token); + var m = new ValueTaskSourceAsTask(t, _token); return #if netstandard m.Task; @@ -588,7 +665,7 @@ namespace System.Threading.Tasks } /// <summary>Type used to create a <see cref="Task{TResult}"/> to represent a <see cref="IValueTaskSource{TResult}"/>.</summary> - private sealed class ValueTaskSourceTask : + private sealed class ValueTaskSourceAsTask : #if netstandard TaskCompletionSource<TResult> #else @@ -597,7 +674,7 @@ namespace System.Threading.Tasks { private static readonly Action<object> s_completionAction = state => { - if (!(state is ValueTaskSourceTask vtst) || + if (!(state is ValueTaskSourceAsTask vtst) || !(vtst._source is IValueTaskSource<TResult> source)) { // This could only happen if the IValueTaskSource<TResult> passed the wrong state @@ -642,7 +719,7 @@ namespace System.Threading.Tasks /// <summary>The token to pass through to operations on <see cref="_source"/></summary> private readonly short _token; - public ValueTaskSourceTask(IValueTaskSource<TResult> source, short token) + public ValueTaskSourceAsTask(IValueTaskSource<TResult> source, short token) { _source = source; _token = token; @@ -654,30 +731,73 @@ namespace System.Threading.Tasks public bool IsCompleted { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _obj == null || (ObjectIsTask ? UnsafeGetTask().IsCompleted : UnsafeGetValueTaskSource().GetStatus(_token) != ValueTaskSourceStatus.Pending); + get + { + object obj = _obj; + Debug.Assert(obj == null || obj is Task<TResult> || obj is IValueTaskSource<TResult>); + + if (obj == null) + { + return true; + } + + if (obj is Task<TResult> t) + { + return t.IsCompleted; + } + + return Unsafe.As<IValueTaskSource<TResult>>(obj).GetStatus(_token) != ValueTaskSourceStatus.Pending; + } } /// <summary>Gets whether the <see cref="ValueTask{TResult}"/> represents a successfully completed operation.</summary> public bool IsCompletedSuccessfully { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => - _obj == null || - (ObjectIsTask ? + get + { + object obj = _obj; + Debug.Assert(obj == null || obj is Task<TResult> || obj is IValueTaskSource<TResult>); + + if (obj == null) + { + return true; + } + + if (obj is Task<TResult> t) + { + return #if netstandard - UnsafeTask.Status == TaskStatus.RanToCompletion : + t.Status == TaskStatus.RanToCompletion; #else - UnsafeGetTask().IsCompletedSuccessfully : + t.IsCompletedSuccessfully; #endif - UnsafeGetValueTaskSource().GetStatus(_token) == ValueTaskSourceStatus.Succeeded); + } + + return Unsafe.As<IValueTaskSource<TResult>>(obj).GetStatus(_token) == ValueTaskSourceStatus.Succeeded; + } } /// <summary>Gets whether the <see cref="ValueTask{TResult}"/> represents a failed operation.</summary> public bool IsFaulted { - get => - _obj != null && - (ObjectIsTask ? UnsafeGetTask().IsFaulted : UnsafeGetValueTaskSource().GetStatus(_token) == ValueTaskSourceStatus.Faulted); + get + { + object obj = _obj; + Debug.Assert(obj == null || obj is Task<TResult> || obj is IValueTaskSource<TResult>); + + if (obj == null) + { + return false; + } + + if (obj is Task<TResult> t) + { + return t.IsFaulted; + } + + return Unsafe.As<IValueTaskSource<TResult>>(obj).GetStatus(_token) == ValueTaskSourceStatus.Faulted; + } } /// <summary>Gets whether the <see cref="ValueTask{TResult}"/> represents a canceled operation.</summary> @@ -688,9 +808,23 @@ namespace System.Threading.Tasks /// </remarks> public bool IsCanceled { - get => - _obj != null && - (ObjectIsTask ? UnsafeGetTask().IsCanceled : UnsafeGetValueTaskSource().GetStatus(_token) == ValueTaskSourceStatus.Canceled); + get + { + object obj = _obj; + Debug.Assert(obj == null || obj is Task<TResult> || obj is IValueTaskSource<TResult>); + + if (obj == null) + { + return false; + } + + if (obj is Task<TResult> t) + { + return t.IsCanceled; + } + + return Unsafe.As<IValueTaskSource<TResult>>(obj).GetStatus(_token) == ValueTaskSourceStatus.Canceled; + } } /// <summary>Gets the result.</summary> @@ -699,23 +833,25 @@ namespace System.Threading.Tasks [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - if (_obj == null) + object obj = _obj; + Debug.Assert(obj == null || obj is Task<TResult> || obj is IValueTaskSource<TResult>); + + if (obj == null) { return _result; } - if (ObjectIsTask) + if (obj is Task<TResult> t) { #if netstandard - return UnsafeTask.GetAwaiter().GetResult(); + return t.GetAwaiter().GetResult(); #else - Task<TResult> t = UnsafeGetTask(); TaskAwaiter.ValidateEnd(t); return t.ResultOnSuccess; #endif } - return UnsafeGetValueTaskSource().GetResult(_token); + return Unsafe.As<IValueTaskSource<TResult>>(obj).GetResult(_token); } } @@ -728,12 +864,8 @@ namespace System.Threading.Tasks /// true to attempt to marshal the continuation back to the captured context; otherwise, false. /// </param> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ConfiguredValueTaskAwaitable<TResult> ConfigureAwait(bool continueOnCapturedContext) - { - // TODO: Simplify once https://github.com/dotnet/coreclr/pull/16138 is fixed. - bool avoidCapture = !continueOnCapturedContext; - return new ConfiguredValueTaskAwaitable<TResult>(new ValueTask<TResult>(_obj, _result, _token, _flags | Unsafe.As<bool, ValueTaskFlags>(ref avoidCapture))); - } + public ConfiguredValueTaskAwaitable<TResult> ConfigureAwait(bool continueOnCapturedContext) => + new ConfiguredValueTaskAwaitable<TResult>(new ValueTask<TResult>(_obj, _result, _token, continueOnCapturedContext)); /// <summary>Gets a string-representation of this <see cref="ValueTask{TResult}"/>.</summary> public override string ToString() @@ -750,26 +882,4 @@ namespace System.Threading.Tasks return string.Empty; } } - - /// <summary>Internal flags used in the implementation of <see cref="ValueTask"/> and <see cref="ValueTask{TResult}"/>.</summary> - [Flags] - internal enum ValueTaskFlags : byte - { - /// <summary> - /// Indicates that context (e.g. SynchronizationContext) should not be captured when adding - /// a continuation. - /// </summary> - /// <remarks> - /// The value here must be 0x1, to match the value of a true Boolean reinterpreted as a byte. - /// This only has meaning when awaiting a ValueTask, with ConfigureAwait creating a new - /// ValueTask setting or not setting this flag appropriately. - /// </remarks> - AvoidCapturedContext = 0x1, - - /// <summary> - /// Indicates that the ValueTask's object field stores a Task. This is used to avoid - /// a type check on whatever is stored in the object field. - /// </summary> - ObjectIsTask = 0x2 - } -}
\ No newline at end of file +} |