diff options
author | Stephen Toub <stoub@microsoft.com> | 2018-03-01 14:14:38 +0300 |
---|---|---|
committer | Stephen Toub <stoub@microsoft.com> | 2018-03-02 01:14:55 +0300 |
commit | d270a3d8f2d70adc5e22a87cbfe8ec3e5ceb8a64 (patch) | |
tree | 40d36c1f50e4d15497f153684c43e44b00f691fb | |
parent | 56566b79b5ec2517b4e9f2c5e6f8805d4ad47151 (diff) |
Merge pull request dotnet/coreclr#16618 from stephentoub/valuetaskextensibility
Implement ValueTask extensibility
Signed-off-by: dotnet-bot <dotnet-bot@microsoft.com>
14 files changed, 1254 insertions, 191 deletions
diff --git a/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems b/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems index b86412112..fafe4b929 100644 --- a/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems +++ b/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems @@ -551,6 +551,7 @@ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\Tasks\TaskToApm.cs" /> <Compile Include="$(MSBuildThisFileDirectory)System\Threading\Tasks\TaskSchedulerException.cs" /> <Compile Include="$(MSBuildThisFileDirectory)System\Threading\Tasks\ValueTask.cs" /> + <Compile Include="$(MSBuildThisFileDirectory)System\Threading\Tasks\Sources\IValueTaskSource.cs" /> <Compile Include="$(MSBuildThisFileDirectory)System\Threading\ThreadAbortException.cs" /> <Compile Include="$(MSBuildThisFileDirectory)System\Threading\ThreadPriority.cs" /> <Compile Include="$(MSBuildThisFileDirectory)System\Threading\ThreadStart.cs" /> diff --git a/src/System.Private.CoreLib/shared/System/IO/FileStream.Unix.cs b/src/System.Private.CoreLib/shared/System/IO/FileStream.Unix.cs index 31b9ac53b..d9fcf6571 100644 --- a/src/System.Private.CoreLib/shared/System/IO/FileStream.Unix.cs +++ b/src/System.Private.CoreLib/shared/System/IO/FileStream.Unix.cs @@ -635,12 +635,12 @@ namespace System.IO /// <param name="source">The buffer to write data from.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param> /// <returns>A task that represents the asynchronous write operation.</returns> - private Task WriteAsyncInternal(ReadOnlyMemory<byte> source, CancellationToken cancellationToken) + private ValueTask WriteAsyncInternal(ReadOnlyMemory<byte> source, CancellationToken cancellationToken) { Debug.Assert(_useAsyncIO); if (cancellationToken.IsCancellationRequested) - return Task.FromCanceled(cancellationToken); + return new ValueTask(Task.FromCanceled(cancellationToken)); if (_fileHandle.IsClosed) throw Error.GetFileNotOpen(); @@ -667,11 +667,11 @@ namespace System.IO source.Span.CopyTo(new Span<byte>(GetBuffer(), _writePos, source.Length)); _writePos += source.Length; - return Task.CompletedTask; + return default; } catch (Exception exc) { - return Task.FromException(exc); + return new ValueTask(Task.FromException(exc)); } finally { @@ -682,7 +682,7 @@ namespace System.IO // Otherwise, issue the whole request asynchronously. _asyncState.ReadOnlyMemory = source; - return waitTask.ContinueWith((t, s) => + return new ValueTask(waitTask.ContinueWith((t, s) => { // The options available on Unix for writing asynchronously to an arbitrary file // handle typically amount to just using another thread to do the synchronous write, @@ -702,7 +702,7 @@ namespace System.IO thisRef.WriteSpan(readOnlyMemory.Span); } finally { thisRef._asyncState.Release(); } - }, this, CancellationToken.None, TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default); + }, this, CancellationToken.None, TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default)); } /// <summary>Sets the current position of this stream to the given value.</summary> diff --git a/src/System.Private.CoreLib/shared/System/IO/FileStream.Windows.cs b/src/System.Private.CoreLib/shared/System/IO/FileStream.Windows.cs index 85f045426..291a30bb5 100644 --- a/src/System.Private.CoreLib/shared/System/IO/FileStream.Windows.cs +++ b/src/System.Private.CoreLib/shared/System/IO/FileStream.Windows.cs @@ -961,7 +961,7 @@ namespace System.IO return completionSource.Task; } - private Task WriteAsyncInternal(ReadOnlyMemory<byte> source, CancellationToken cancellationToken) + private ValueTask WriteAsyncInternal(ReadOnlyMemory<byte> source, CancellationToken cancellationToken) { Debug.Assert(_useAsyncIO); Debug.Assert((_readPos == 0 && _readLength == 0 && _writePos >= 0) || (_writePos == 0 && _readPos <= _readLength), "We're either reading or writing, but not both."); @@ -1005,7 +1005,7 @@ namespace System.IO // completely, we want to do the asynchronous flush/write as part of this operation // rather than waiting until the next write that fills the buffer. if (source.Length != remainingBuffer) - return Task.CompletedTask; + return default; Debug.Assert(_writePos == _bufferLength); } @@ -1051,7 +1051,7 @@ namespace System.IO flushTask.IsFaulted || flushTask.IsCanceled) { - return flushTask; + return new ValueTask(flushTask); } } @@ -1061,10 +1061,10 @@ namespace System.IO // Finally, issue the write asynchronously, and return a Task that logically // represents the write operation, including any flushing done. Task writeTask = WriteAsyncInternalCore(source, cancellationToken); - return + return new ValueTask( (flushTask == null || flushTask.Status == TaskStatus.RanToCompletion) ? writeTask : (writeTask.Status == TaskStatus.RanToCompletion) ? flushTask : - Task.WhenAll(flushTask, writeTask); + Task.WhenAll(flushTask, writeTask)); } private unsafe Task WriteAsyncInternalCore(ReadOnlyMemory<byte> source, CancellationToken cancellationToken) @@ -1319,7 +1319,7 @@ namespace System.IO int bufferedBytes = _readLength - _readPos; if (bufferedBytes > 0) { - await destination.WriteAsync(GetBuffer(), _readPos, bufferedBytes, cancellationToken).ConfigureAwait(false); + await destination.WriteAsync(new ReadOnlyMemory<byte>(GetBuffer(), _readPos, bufferedBytes), cancellationToken).ConfigureAwait(false); _readPos = _readLength = 0; } } @@ -1345,7 +1345,6 @@ namespace System.IO // Further, typically the CopyToAsync buffer size will be larger than that used by the FileStream, such that // we'd likely be unable to use it anyway. Instead, we rent the buffer from a pool. byte[] copyBuffer = ArrayPool<byte>.Shared.Rent(bufferSize); - bufferSize = 0; // repurpose bufferSize to be the high water mark for the buffer, to avoid an extra field in the state machine // Allocate an Overlapped we can use repeatedly for all operations var awaitableOverlapped = new PreAllocatedOverlapped(AsyncCopyToAwaitable.s_callback, readAwaitable, copyBuffer); @@ -1452,13 +1451,6 @@ namespace System.IO { readAwaitable._position += numBytesRead; } - - // (and keep track of the maximum number of bytes in the buffer we used, to avoid excessive and unnecessary - // clearing of the buffer before we return it to the pool) - if (numBytesRead > bufferSize) - { - bufferSize = numBytesRead; - } } finally { @@ -1479,7 +1471,7 @@ namespace System.IO } // Write out the read data. - await destination.WriteAsync(copyBuffer, 0, (int)readAwaitable._numBytes, cancellationToken).ConfigureAwait(false); + await destination.WriteAsync(new ReadOnlyMemory<byte>(copyBuffer, 0, (int)readAwaitable._numBytes), cancellationToken).ConfigureAwait(false); } } finally @@ -1488,8 +1480,7 @@ namespace System.IO cancellationReg.Dispose(); awaitableOverlapped.Dispose(); - Array.Clear(copyBuffer, 0, bufferSize); - ArrayPool<byte>.Shared.Return(copyBuffer, clearArray: false); + ArrayPool<byte>.Shared.Return(copyBuffer); // Make sure the stream's current position reflects where we ended up if (!_fileHandle.IsClosed && CanSeek) diff --git a/src/System.Private.CoreLib/shared/System/IO/FileStream.cs b/src/System.Private.CoreLib/shared/System/IO/FileStream.cs index 4593768bd..717b73ff1 100644 --- a/src/System.Private.CoreLib/shared/System/IO/FileStream.cs +++ b/src/System.Private.CoreLib/shared/System/IO/FileStream.cs @@ -458,10 +458,10 @@ namespace System.IO if (IsClosed) throw Error.GetFileNotOpen(); - return WriteAsyncInternal(new ReadOnlyMemory<byte>(buffer, offset, count), cancellationToken); + return WriteAsyncInternal(new ReadOnlyMemory<byte>(buffer, offset, count), cancellationToken).AsTask(); } - public override Task WriteAsync(ReadOnlyMemory<byte> source, CancellationToken cancellationToken = default(CancellationToken)) + public override ValueTask WriteAsync(ReadOnlyMemory<byte> source, CancellationToken cancellationToken = default(CancellationToken)) { if (!_useAsyncIO || GetType() != typeof(FileStream)) { @@ -473,7 +473,7 @@ namespace System.IO if (cancellationToken.IsCancellationRequested) { - return Task.FromCanceled<int>(cancellationToken); + return new ValueTask(Task.FromCanceled<int>(cancellationToken)); } if (IsClosed) @@ -853,7 +853,7 @@ namespace System.IO if (!IsAsync) return base.BeginWrite(array, offset, numBytes, callback, state); else - return TaskToApm.Begin(WriteAsyncInternal(new ReadOnlyMemory<byte>(array, offset, numBytes), CancellationToken.None), callback, state); + return TaskToApm.Begin(WriteAsyncInternal(new ReadOnlyMemory<byte>(array, offset, numBytes), CancellationToken.None).AsTask(), callback, state); } public override int EndRead(IAsyncResult asyncResult) diff --git a/src/System.Private.CoreLib/shared/System/IO/MemoryStream.cs b/src/System.Private.CoreLib/shared/System/IO/MemoryStream.cs index c5e5ea918..8e573b749 100644 --- a/src/System.Private.CoreLib/shared/System/IO/MemoryStream.cs +++ b/src/System.Private.CoreLib/shared/System/IO/MemoryStream.cs @@ -752,11 +752,11 @@ namespace System.IO } } - public override Task WriteAsync(ReadOnlyMemory<byte> source, CancellationToken cancellationToken = default(CancellationToken)) + public override ValueTask WriteAsync(ReadOnlyMemory<byte> source, CancellationToken cancellationToken = default(CancellationToken)) { if (cancellationToken.IsCancellationRequested) { - return Task.FromCanceled(cancellationToken); + return new ValueTask(Task.FromCanceled(cancellationToken)); } try @@ -771,15 +771,15 @@ namespace System.IO { Write(source.Span); } - return Task.CompletedTask; + return default; } catch (OperationCanceledException oce) { - return Task.FromCancellation<VoidTaskResult>(oce); + return new ValueTask(Task.FromCancellation<VoidTaskResult>(oce)); } catch (Exception exception) { - return Task.FromException(exception); + return new ValueTask(Task.FromException(exception)); } } diff --git a/src/System.Private.CoreLib/shared/System/IO/StreamReader.cs b/src/System.Private.CoreLib/shared/System/IO/StreamReader.cs index 4e724ddb3..22ec6e645 100644 --- a/src/System.Private.CoreLib/shared/System/IO/StreamReader.cs +++ b/src/System.Private.CoreLib/shared/System/IO/StreamReader.cs @@ -1091,7 +1091,7 @@ namespace System.IO { Debug.Assert(_bytePos <= _encoding.Preamble.Length, "possible bug in _compressPreamble. Are two threads using this StreamReader at the same time?"); int tmpBytePos = _bytePos; - int len = await tmpStream.ReadAsync(tmpByteBuffer, tmpBytePos, tmpByteBuffer.Length - tmpBytePos, cancellationToken).ConfigureAwait(false); + int len = await tmpStream.ReadAsync(new Memory<byte>(tmpByteBuffer, tmpBytePos, tmpByteBuffer.Length - tmpBytePos), cancellationToken).ConfigureAwait(false); Debug.Assert(len >= 0, "Stream.Read returned a negative number! This is a bug in your stream class."); if (len == 0) @@ -1127,7 +1127,7 @@ namespace System.IO { Debug.Assert(_bytePos == 0, "_bytePos can be non zero only when we are trying to _checkPreamble. Are two threads using this StreamReader at the same time?"); - _byteLen = await tmpStream.ReadAsync(tmpByteBuffer, 0, tmpByteBuffer.Length, cancellationToken).ConfigureAwait(false); + _byteLen = await tmpStream.ReadAsync(new Memory<byte>(tmpByteBuffer), cancellationToken).ConfigureAwait(false); Debug.Assert(_byteLen >= 0, "Stream.Read returned a negative number! This is a bug in your stream class."); @@ -1303,7 +1303,7 @@ namespace System.IO { Debug.Assert(_bytePos <= _encoding.Preamble.Length, "possible bug in _compressPreamble. Are two threads using this StreamReader at the same time?"); int tmpBytePos = _bytePos; - int len = await tmpStream.ReadAsync(tmpByteBuffer, tmpBytePos, tmpByteBuffer.Length - tmpBytePos).ConfigureAwait(false); + int len = await tmpStream.ReadAsync(new Memory<byte>(tmpByteBuffer, tmpBytePos, tmpByteBuffer.Length - tmpBytePos)).ConfigureAwait(false); Debug.Assert(len >= 0, "Stream.Read returned a negative number! This is a bug in your stream class."); if (len == 0) @@ -1325,7 +1325,7 @@ namespace System.IO else { Debug.Assert(_bytePos == 0, "_bytePos can be non zero only when we are trying to _checkPreamble. Are two threads using this StreamReader at the same time?"); - _byteLen = await tmpStream.ReadAsync(tmpByteBuffer, 0, tmpByteBuffer.Length).ConfigureAwait(false); + _byteLen = await tmpStream.ReadAsync(new Memory<byte>(tmpByteBuffer)).ConfigureAwait(false); Debug.Assert(_byteLen >= 0, "Stream.Read returned a negative number! Bug in stream class."); if (_byteLen == 0) // We're at EOF diff --git a/src/System.Private.CoreLib/shared/System/IO/StreamWriter.cs b/src/System.Private.CoreLib/shared/System/IO/StreamWriter.cs index 6cdcd6952..a37624428 100644 --- a/src/System.Private.CoreLib/shared/System/IO/StreamWriter.cs +++ b/src/System.Private.CoreLib/shared/System/IO/StreamWriter.cs @@ -963,14 +963,14 @@ namespace System.IO byte[] preamble = encoding.GetPreamble(); if (preamble.Length > 0) { - await stream.WriteAsync(preamble, 0, preamble.Length, cancellationToken).ConfigureAwait(false); + await stream.WriteAsync(new ReadOnlyMemory<byte>(preamble), cancellationToken).ConfigureAwait(false); } } int count = encoder.GetBytes(charBuffer, 0, charPos, byteBuffer, 0, flushEncoder); if (count > 0) { - await stream.WriteAsync(byteBuffer, 0, count, cancellationToken).ConfigureAwait(false); + await stream.WriteAsync(new ReadOnlyMemory<byte>(byteBuffer, 0, count), cancellationToken).ConfigureAwait(false); } // By definition, calling Flush should flush the stream, but this is diff --git a/src/System.Private.CoreLib/shared/System/IO/UnmanagedMemoryStream.cs b/src/System.Private.CoreLib/shared/System/IO/UnmanagedMemoryStream.cs index 171113542..2f0f34afe 100644 --- a/src/System.Private.CoreLib/shared/System/IO/UnmanagedMemoryStream.cs +++ b/src/System.Private.CoreLib/shared/System/IO/UnmanagedMemoryStream.cs @@ -783,11 +783,11 @@ namespace System.IO /// </summary> /// <param name="buffer">Buffer that will be written.</param> /// <param name="cancellationToken">Token that can be used to cancel the operation.</param> - public override Task WriteAsync(ReadOnlyMemory<byte> source, CancellationToken cancellationToken = default(CancellationToken)) + public override ValueTask WriteAsync(ReadOnlyMemory<byte> source, CancellationToken cancellationToken = default(CancellationToken)) { if (cancellationToken.IsCancellationRequested) { - return Task.FromCanceled(cancellationToken); + return new ValueTask(Task.FromCanceled(cancellationToken)); } try @@ -802,11 +802,11 @@ namespace System.IO { Write(source.Span); } - return Task.CompletedTask; + return default; } catch (Exception ex) { - return Task.FromException(ex); + return new ValueTask(Task.FromException(ex)); } } diff --git a/src/System.Private.CoreLib/shared/System/IO/UnmanagedMemoryStreamWrapper.cs b/src/System.Private.CoreLib/shared/System/IO/UnmanagedMemoryStreamWrapper.cs index 90bb21ac5..f34c3c413 100644 --- a/src/System.Private.CoreLib/shared/System/IO/UnmanagedMemoryStreamWrapper.cs +++ b/src/System.Private.CoreLib/shared/System/IO/UnmanagedMemoryStreamWrapper.cs @@ -217,7 +217,7 @@ namespace System.IO return _unmanagedStream.WriteAsync(buffer, offset, count, cancellationToken); } - public override Task WriteAsync(ReadOnlyMemory<byte> source, CancellationToken cancellationToken = default(CancellationToken)) + public override ValueTask WriteAsync(ReadOnlyMemory<byte> source, CancellationToken cancellationToken = default(CancellationToken)) { return _unmanagedStream.WriteAsync(source, cancellationToken); } diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs index 49cdaccb0..0e1220d11 100644 --- a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs +++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs @@ -8,6 +8,108 @@ using System.Threading.Tasks; namespace System.Runtime.CompilerServices { + /// <summary>Represents a builder for asynchronous methods that return a <see cref="ValueTask"/>.</summary> + [StructLayout(LayoutKind.Auto)] + public struct AsyncValueTaskMethodBuilder + { + /// <summary>The <see cref="AsyncTaskMethodBuilder"/> to which most operations are delegated.</summary> + private AsyncTaskMethodBuilder _methodBuilder; // mutable struct; do not make it readonly + /// <summary>true if completed synchronously and successfully; otherwise, false.</summary> + private bool _haveResult; + /// <summary>true if the builder should be used for setting/getting the result; otherwise, false.</summary> + private bool _useBuilder; + + /// <summary>Creates an instance of the <see cref="AsyncValueTaskMethodBuilder"/> struct.</summary> + /// <returns>The initialized instance.</returns> + public static AsyncValueTaskMethodBuilder Create() => +#if CORERT + // corert's AsyncTaskMethodBuilder.Create() currently does additional debugger-related + // work, so we need to delegate to it. + new AsyncValueTaskMethodBuilder() { _methodBuilder = AsyncTaskMethodBuilder.Create() }; +#else + // _methodBuilder should be initialized to AsyncTaskMethodBuilder.Create(), but on coreclr + // that Create() is a nop, so we can just return the default here. + default; +#endif + + /// <summary>Begins running the builder with the associated state machine.</summary> + /// <typeparam name="TStateMachine">The type of the state machine.</typeparam> + /// <param name="stateMachine">The state machine instance, passed by reference.</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine => + // will provide the right ExecutionContext semantics +#if netstandard + _methodBuilder.Start(ref stateMachine); +#else + AsyncMethodBuilderCore.Start(ref stateMachine); +#endif + + /// <summary>Associates the builder with the specified state machine.</summary> + /// <param name="stateMachine">The state machine instance to associate with the builder.</param> + public void SetStateMachine(IAsyncStateMachine stateMachine) => _methodBuilder.SetStateMachine(stateMachine); + + /// <summary>Marks the task as successfully completed.</summary> + public void SetResult() + { + if (_useBuilder) + { + _methodBuilder.SetResult(); + } + else + { + _haveResult = true; + } + } + + /// <summary>Marks the task as failed and binds the specified exception to the task.</summary> + /// <param name="exception">The exception to bind to the task.</param> + public void SetException(Exception exception) => _methodBuilder.SetException(exception); + + /// <summary>Gets the task for this builder.</summary> + public ValueTask Task + { + get + { + if (_haveResult) + { + return default; + } + else + { + _useBuilder = true; + return new ValueTask(_methodBuilder.Task); + } + } + } + + /// <summary>Schedules the state machine to proceed to the next action when the specified awaiter completes.</summary> + /// <typeparam name="TAwaiter">The type of the awaiter.</typeparam> + /// <typeparam name="TStateMachine">The type of the state machine.</typeparam> + /// <param name="awaiter">The awaiter.</param> + /// <param name="stateMachine">The state machine.</param> + public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) + where TAwaiter : INotifyCompletion + where TStateMachine : IAsyncStateMachine + { + _useBuilder = true; + _methodBuilder.AwaitOnCompleted(ref awaiter, ref stateMachine); + } + + /// <summary>Schedules the state machine to proceed to the next action when the specified awaiter completes.</summary> + /// <typeparam name="TAwaiter">The type of the awaiter.</typeparam> + /// <typeparam name="TStateMachine">The type of the state machine.</typeparam> + /// <param name="awaiter">The awaiter.</param> + /// <param name="stateMachine">The state machine.</param> + [SecuritySafeCritical] + public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) + where TAwaiter : ICriticalNotifyCompletion + where TStateMachine : IAsyncStateMachine + { + _useBuilder = true; + _methodBuilder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); + } + } + /// <summary>Represents a builder for asynchronous methods that returns a <see cref="ValueTask{TResult}"/>.</summary> /// <typeparam name="TResult">The type of the result.</typeparam> [StructLayout(LayoutKind.Auto)] @@ -32,7 +134,7 @@ namespace System.Runtime.CompilerServices #else // _methodBuilder should be initialized to AsyncTaskMethodBuilder<TResult>.Create(), but on coreclr // that Create() is a nop, so we can just return the default here. - default(AsyncValueTaskMethodBuilder<TResult>); + default; #endif /// <summary>Begins running the builder with the associated state machine.</summary> diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/ConfiguredValueTaskAwaitable.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/ConfiguredValueTaskAwaitable.cs index f22b9d94b..65d3d5670 100644 --- a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/ConfiguredValueTaskAwaitable.cs +++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/ConfiguredValueTaskAwaitable.cs @@ -5,9 +5,115 @@ using System.Diagnostics; using System.Runtime.InteropServices; using System.Threading.Tasks; +using System.Threading.Tasks.Sources; + +#if !netstandard +using Internal.Runtime.CompilerServices; +#endif namespace System.Runtime.CompilerServices { + /// <summary>Provides an awaitable type that enables configured awaits on a <see cref="ValueTask"/>.</summary> + [StructLayout(LayoutKind.Auto)] + public readonly struct ConfiguredValueTaskAwaitable + { + /// <summary>The wrapped <see cref="Task"/>.</summary> + private readonly ValueTask _value; + + /// <summary>Initializes the awaitable.</summary> + /// <param name="value">The wrapped <see cref="ValueTask"/>.</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ConfiguredValueTaskAwaitable(ValueTask value) => _value = value; + + /// <summary>Returns an awaiter for this <see cref="ConfiguredValueTaskAwaitable"/> instance.</summary> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ConfiguredValueTaskAwaiter GetAwaiter() => new ConfiguredValueTaskAwaiter(_value); + + /// <summary>Provides an awaiter for a <see cref="ConfiguredValueTaskAwaitable"/>.</summary> + [StructLayout(LayoutKind.Auto)] + public readonly struct ConfiguredValueTaskAwaiter : ICriticalNotifyCompletion +#if CORECLR + , IValueTaskAwaiter +#endif + { + /// <summary>The value being awaited.</summary> + private readonly ValueTask _value; + + /// <summary>Initializes the awaiter.</summary> + /// <param name="value">The value to be awaited.</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ConfiguredValueTaskAwaiter(ValueTask value) => _value = value; + + /// <summary>Gets whether the <see cref="ConfiguredValueTaskAwaitable"/> has completed.</summary> + public bool IsCompleted + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _value.IsCompleted; + } + + /// <summary>Gets the result of the ValueTask.</summary> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [StackTraceHidden] + public void GetResult() => _value.ThrowIfCompletedUnsuccessfully(); + + /// <summary>Schedules the continuation action for the <see cref="ConfiguredValueTaskAwaitable"/>.</summary> + public void OnCompleted(Action continuation) + { + if (_value.ObjectIsTask) + { + _value.UnsafeTask.ConfigureAwait(_value.ContinueOnCapturedContext).GetAwaiter().OnCompleted(continuation); + } + else if (_value._obj != null) + { + _value.UnsafeValueTaskSource.OnCompleted(ValueTaskAwaiter.s_invokeActionDelegate, continuation, _value._token, + ValueTaskSourceOnCompletedFlags.FlowExecutionContext | + (_value.ContinueOnCapturedContext ? ValueTaskSourceOnCompletedFlags.UseSchedulingContext : ValueTaskSourceOnCompletedFlags.None)); + } + else + { + Task.CompletedTask.ConfigureAwait(_value.ContinueOnCapturedContext).GetAwaiter().OnCompleted(continuation); + } + } + + /// <summary>Schedules the continuation action for the <see cref="ConfiguredValueTaskAwaitable"/>.</summary> + public void UnsafeOnCompleted(Action continuation) + { + if (_value.ObjectIsTask) + { + _value.UnsafeTask.ConfigureAwait(_value.ContinueOnCapturedContext).GetAwaiter().UnsafeOnCompleted(continuation); + } + else if (_value._obj != null) + { + _value.UnsafeValueTaskSource.OnCompleted(ValueTaskAwaiter.s_invokeActionDelegate, continuation, _value._token, + _value.ContinueOnCapturedContext ? ValueTaskSourceOnCompletedFlags.UseSchedulingContext : ValueTaskSourceOnCompletedFlags.None); + } + else + { + Task.CompletedTask.ConfigureAwait(_value.ContinueOnCapturedContext).GetAwaiter().UnsafeOnCompleted(continuation); + } + } + +#if CORECLR + void IValueTaskAwaiter.AwaitUnsafeOnCompleted(IAsyncStateMachineBox box) + { + if (_value.ObjectIsTask) + { + TaskAwaiter.UnsafeOnCompletedInternal(_value.UnsafeTask, box, _value.ContinueOnCapturedContext); + } + else if (_value._obj != null) + { + _value.UnsafeValueTaskSource.OnCompleted(ValueTaskAwaiter.s_invokeAsyncStateMachineBox, box, _value._token, + _value.ContinueOnCapturedContext ? ValueTaskSourceOnCompletedFlags.UseSchedulingContext : ValueTaskSourceOnCompletedFlags.None); + } + else + { + TaskAwaiter.UnsafeOnCompletedInternal(Task.CompletedTask, box, _value.ContinueOnCapturedContext); + } + } +#endif + } + } + /// <summary>Provides an awaitable type that enables configured awaits on a <see cref="ValueTask{TResult}"/>.</summary> /// <typeparam name="TResult">The type of the result produced.</typeparam> [StructLayout(LayoutKind.Auto)] @@ -15,78 +121,98 @@ namespace System.Runtime.CompilerServices { /// <summary>The wrapped <see cref="ValueTask{TResult}"/>.</summary> private readonly ValueTask<TResult> _value; - /// <summary>true to attempt to marshal the continuation back to the original context captured; otherwise, false.</summary> - private readonly bool _continueOnCapturedContext; /// <summary>Initializes the awaitable.</summary> /// <param name="value">The wrapped <see cref="ValueTask{TResult}"/>.</param> - /// <param name="continueOnCapturedContext"> - /// true to attempt to marshal the continuation back to the original synchronization context captured; otherwise, false. - /// </param> - internal ConfiguredValueTaskAwaitable(ValueTask<TResult> value, bool continueOnCapturedContext) - { - _value = value; - _continueOnCapturedContext = continueOnCapturedContext; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ConfiguredValueTaskAwaitable(ValueTask<TResult> value) => _value = value; /// <summary>Returns an awaiter for this <see cref="ConfiguredValueTaskAwaitable{TResult}"/> instance.</summary> - public ConfiguredValueTaskAwaiter GetAwaiter() => - new ConfiguredValueTaskAwaiter(_value, _continueOnCapturedContext); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ConfiguredValueTaskAwaiter GetAwaiter() => new ConfiguredValueTaskAwaiter(_value); /// <summary>Provides an awaiter for a <see cref="ConfiguredValueTaskAwaitable{TResult}"/>.</summary> [StructLayout(LayoutKind.Auto)] - public readonly struct ConfiguredValueTaskAwaiter : ICriticalNotifyCompletion, IConfiguredValueTaskAwaiter + public readonly struct ConfiguredValueTaskAwaiter : ICriticalNotifyCompletion +#if CORECLR + , IValueTaskAwaiter +#endif { /// <summary>The value being awaited.</summary> private readonly ValueTask<TResult> _value; - /// <summary>The value to pass to ConfigureAwait.</summary> - internal readonly bool _continueOnCapturedContext; /// <summary>Initializes the awaiter.</summary> /// <param name="value">The value to be awaited.</param> - /// <param name="continueOnCapturedContext">The value to pass to ConfigureAwait.</param> - internal ConfiguredValueTaskAwaiter(ValueTask<TResult> value, bool continueOnCapturedContext) - { - _value = value; - _continueOnCapturedContext = continueOnCapturedContext; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ConfiguredValueTaskAwaiter(ValueTask<TResult> value) => _value = value; /// <summary>Gets whether the <see cref="ConfiguredValueTaskAwaitable{TResult}"/> has completed.</summary> - public bool IsCompleted => _value.IsCompleted; + public bool IsCompleted + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _value.IsCompleted; + } /// <summary>Gets the result of the ValueTask.</summary> + [MethodImpl(MethodImplOptions.AggressiveInlining)] [StackTraceHidden] - public TResult GetResult() => - _value._task == null ? - _value._result : - _value._task.GetAwaiter().GetResult(); + public TResult GetResult() => _value.Result; /// <summary>Schedules the continuation action for the <see cref="ConfiguredValueTaskAwaitable{TResult}"/>.</summary> - public void OnCompleted(Action continuation) => - _value.AsTask().ConfigureAwait(_continueOnCapturedContext).GetAwaiter().OnCompleted(continuation); + public void OnCompleted(Action continuation) + { + if (_value.ObjectIsTask) + { + _value.UnsafeTask.ConfigureAwait(_value.ContinueOnCapturedContext).GetAwaiter().OnCompleted(continuation); + } + else if (_value._obj != null) + { + _value.UnsafeValueTaskSource.OnCompleted(ValueTaskAwaiter.s_invokeActionDelegate, continuation, _value._token, + ValueTaskSourceOnCompletedFlags.FlowExecutionContext | + (_value.ContinueOnCapturedContext ? ValueTaskSourceOnCompletedFlags.UseSchedulingContext : ValueTaskSourceOnCompletedFlags.None)); + } + else + { + Task.CompletedTask.ConfigureAwait(_value.ContinueOnCapturedContext).GetAwaiter().OnCompleted(continuation); + } + } /// <summary>Schedules the continuation action for the <see cref="ConfiguredValueTaskAwaitable{TResult}"/>.</summary> - public void UnsafeOnCompleted(Action continuation) => - _value.AsTask().ConfigureAwait(_continueOnCapturedContext).GetAwaiter().UnsafeOnCompleted(continuation); - - /// <summary>Gets the task underlying <see cref="_value"/>.</summary> - internal Task<TResult> AsTask() => _value.AsTask(); + public void UnsafeOnCompleted(Action continuation) + { + if (_value.ObjectIsTask) + { + _value.UnsafeTask.ConfigureAwait(_value.ContinueOnCapturedContext).GetAwaiter().UnsafeOnCompleted(continuation); + } + else if (_value._obj != null) + { + _value.UnsafeValueTaskSource.OnCompleted(ValueTaskAwaiter.s_invokeActionDelegate, continuation, _value._token, + _value.ContinueOnCapturedContext ? ValueTaskSourceOnCompletedFlags.UseSchedulingContext : ValueTaskSourceOnCompletedFlags.None); + } + else + { + Task.CompletedTask.ConfigureAwait(_value.ContinueOnCapturedContext).GetAwaiter().UnsafeOnCompleted(continuation); + } + } - /// <summary>Gets the task underlying the incomplete <see cref="_value"/>.</summary> - /// <remarks>This method is used when awaiting and IsCompleted returned false; thus we expect the value task to be wrapping a non-null task.</remarks> - Task IConfiguredValueTaskAwaiter.GetTask(out bool continueOnCapturedContext) +#if CORECLR + void IValueTaskAwaiter.AwaitUnsafeOnCompleted(IAsyncStateMachineBox box) { - continueOnCapturedContext = _continueOnCapturedContext; - return _value.AsTaskExpectNonNull(); + if (_value.ObjectIsTask) + { + TaskAwaiter.UnsafeOnCompletedInternal(_value.UnsafeTask, box, _value.ContinueOnCapturedContext); + } + else if (_value._obj != null) + { + _value.UnsafeValueTaskSource.OnCompleted(ValueTaskAwaiter.s_invokeAsyncStateMachineBox, box, _value._token, + _value.ContinueOnCapturedContext ? ValueTaskSourceOnCompletedFlags.UseSchedulingContext : ValueTaskSourceOnCompletedFlags.None); + } + else + { + TaskAwaiter.UnsafeOnCompletedInternal(Task.CompletedTask, box, _value.ContinueOnCapturedContext); + } } +#endif } } - - /// <summary> - /// Internal interface used to enable extract the Task from arbitrary configured ValueTask awaiters. - /// </summary> - internal interface IConfiguredValueTaskAwaiter - { - Task GetTask(out bool continueOnCapturedContext); - } } diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/ValueTaskAwaiter.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/ValueTaskAwaiter.cs index 3f212d8bf..0414a05a0 100644 --- a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/ValueTaskAwaiter.cs +++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/ValueTaskAwaiter.cs @@ -4,50 +4,198 @@ using System.Diagnostics; using System.Threading.Tasks; +using System.Threading.Tasks.Sources; namespace System.Runtime.CompilerServices { + /// <summary>Provides an awaiter for a <see cref="ValueTask"/>.</summary> + public readonly struct ValueTaskAwaiter : ICriticalNotifyCompletion +#if CORECLR + , IValueTaskAwaiter +#endif + { + /// <summary>Shim used to invoke an <see cref="Action"/> passed as the state argument to a <see cref="Action{Object}"/>.</summary> + internal static readonly Action<object> s_invokeActionDelegate = state => + { + if (!(state is Action action)) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.state); + return; + } + + action(); + }; + /// <summary>The value being awaited.</summary> + private readonly ValueTask _value; + + /// <summary>Initializes the awaiter.</summary> + /// <param name="value">The value to be awaited.</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ValueTaskAwaiter(ValueTask value) => _value = value; + + /// <summary>Gets whether the <see cref="ValueTask"/> has completed.</summary> + public bool IsCompleted + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _value.IsCompleted; + } + + /// <summary>Gets the result of the ValueTask.</summary> + [StackTraceHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void GetResult() => _value.ThrowIfCompletedUnsuccessfully(); + + /// <summary>Schedules the continuation action for this ValueTask.</summary> + public void OnCompleted(Action continuation) + { + if (_value.ObjectIsTask) + { + _value.UnsafeTask.GetAwaiter().OnCompleted(continuation); + } + else if (_value._obj != null) + { + _value.UnsafeValueTaskSource.OnCompleted(s_invokeActionDelegate, continuation, _value._token, ValueTaskSourceOnCompletedFlags.UseSchedulingContext | ValueTaskSourceOnCompletedFlags.FlowExecutionContext); + } + else + { + Task.CompletedTask.GetAwaiter().OnCompleted(continuation); + } + } + + /// <summary>Schedules the continuation action for this ValueTask.</summary> + public void UnsafeOnCompleted(Action continuation) + { + if (_value.ObjectIsTask) + { + _value.UnsafeTask.GetAwaiter().UnsafeOnCompleted(continuation); + } + else if (_value._obj != null) + { + _value.UnsafeValueTaskSource.OnCompleted(s_invokeActionDelegate, continuation, _value._token, ValueTaskSourceOnCompletedFlags.UseSchedulingContext); + } + else + { + Task.CompletedTask.GetAwaiter().UnsafeOnCompleted(continuation); + } + } + +#if CORECLR + void IValueTaskAwaiter.AwaitUnsafeOnCompleted(IAsyncStateMachineBox box) + { + if (_value.ObjectIsTask) + { + TaskAwaiter.UnsafeOnCompletedInternal(_value.UnsafeTask, box, continueOnCapturedContext: true); + } + else if (_value._obj != null) + { + _value.UnsafeValueTaskSource.OnCompleted(s_invokeAsyncStateMachineBox, box, _value._token, ValueTaskSourceOnCompletedFlags.UseSchedulingContext); + } + else + { + TaskAwaiter.UnsafeOnCompletedInternal(Task.CompletedTask, box, continueOnCapturedContext: true); + } + } + + /// <summary>Shim used to invoke <see cref="ITaskCompletionAction.Invoke"/> of the supplied <see cref="IAsyncStateMachineBox"/>.</summary> + internal static readonly Action<object> s_invokeAsyncStateMachineBox = state => + { + if (!(state is IAsyncStateMachineBox box)) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.state); + return; + } + + box.Invoke(null); + }; +#endif + } + /// <summary>Provides an awaiter for a <see cref="ValueTask{TResult}"/>.</summary> - public readonly struct ValueTaskAwaiter<TResult> : ICriticalNotifyCompletion, IValueTaskAwaiter + public readonly struct ValueTaskAwaiter<TResult> : ICriticalNotifyCompletion +#if CORECLR + , IValueTaskAwaiter +#endif { /// <summary>The value being awaited.</summary> private readonly ValueTask<TResult> _value; /// <summary>Initializes the awaiter.</summary> /// <param name="value">The value to be awaited.</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal ValueTaskAwaiter(ValueTask<TResult> value) => _value = value; /// <summary>Gets whether the <see cref="ValueTask{TResult}"/> has completed.</summary> - public bool IsCompleted => _value.IsCompleted; + public bool IsCompleted + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _value.IsCompleted; + } /// <summary>Gets the result of the ValueTask.</summary> [StackTraceHidden] - public TResult GetResult() => - _value._task == null ? - _value._result : - _value._task.GetAwaiter().GetResult(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public TResult GetResult() => _value.Result; /// <summary>Schedules the continuation action for this ValueTask.</summary> - public void OnCompleted(Action continuation) => - _value.AsTask().ConfigureAwait(continueOnCapturedContext: true).GetAwaiter().OnCompleted(continuation); + public void OnCompleted(Action continuation) + { + if (_value.ObjectIsTask) + { + _value.UnsafeTask.GetAwaiter().OnCompleted(continuation); + } + else if (_value._obj != null) + { + _value.UnsafeValueTaskSource.OnCompleted(ValueTaskAwaiter.s_invokeActionDelegate, continuation, _value._token, ValueTaskSourceOnCompletedFlags.UseSchedulingContext | ValueTaskSourceOnCompletedFlags.FlowExecutionContext); + } + else + { + Task.CompletedTask.GetAwaiter().OnCompleted(continuation); + } + } /// <summary>Schedules the continuation action for this ValueTask.</summary> - public void UnsafeOnCompleted(Action continuation) => - _value.AsTask().ConfigureAwait(continueOnCapturedContext: true).GetAwaiter().UnsafeOnCompleted(continuation); - - /// <summary>Gets the task underlying <see cref="_value"/>.</summary> - internal Task<TResult> AsTask() => _value.AsTask(); + public void UnsafeOnCompleted(Action continuation) + { + if (_value.ObjectIsTask) + { + _value.UnsafeTask.GetAwaiter().UnsafeOnCompleted(continuation); + } + else if (_value._obj != null) + { + _value.UnsafeValueTaskSource.OnCompleted(ValueTaskAwaiter.s_invokeActionDelegate, continuation, _value._token, ValueTaskSourceOnCompletedFlags.UseSchedulingContext); + } + else + { + Task.CompletedTask.GetAwaiter().UnsafeOnCompleted(continuation); + } + } - /// <summary>Gets the task underlying the incomplete <see cref="_value"/>.</summary> - /// <remarks>This method is used when awaiting and IsCompleted returned false; thus we expect the value task to be wrapping a non-null task.</remarks> - Task IValueTaskAwaiter.GetTask() => _value.AsTaskExpectNonNull(); +#if CORECLR + void IValueTaskAwaiter.AwaitUnsafeOnCompleted(IAsyncStateMachineBox box) + { + if (_value.ObjectIsTask) + { + TaskAwaiter.UnsafeOnCompletedInternal(_value.UnsafeTask, box, continueOnCapturedContext: true); + } + else if (_value._obj != null) + { + _value.UnsafeValueTaskSource.OnCompleted(ValueTaskAwaiter.s_invokeAsyncStateMachineBox, box, _value._token, ValueTaskSourceOnCompletedFlags.UseSchedulingContext); + } + else + { + TaskAwaiter.UnsafeOnCompletedInternal(Task.CompletedTask, box, continueOnCapturedContext: true); + } + } +#endif } - /// <summary> - /// Internal interface used to enable extract the Task from arbitrary ValueTask awaiters. - /// </summary>> +#if CORECLR + /// <summary>Internal interface used to enable optimizations from <see cref="AsyncTaskMethodBuilder"/> on <see cref="ValueTask"/>.</summary>> internal interface IValueTaskAwaiter { - Task GetTask(); + /// <summary>Invoked to set <see cref="ITaskCompletionAction.Invoke"/> of the <paramref name="box"/> as the awaiter's continuation.</summary> + /// <param name="box">The box object.</param> + void AwaitUnsafeOnCompleted(IAsyncStateMachineBox box); } +#endif } diff --git a/src/System.Private.CoreLib/shared/System/Threading/Tasks/Sources/IValueTaskSource.cs b/src/System.Private.CoreLib/shared/System/Threading/Tasks/Sources/IValueTaskSource.cs new file mode 100644 index 000000000..3c1e8830a --- /dev/null +++ b/src/System.Private.CoreLib/shared/System/Threading/Tasks/Sources/IValueTaskSource.cs @@ -0,0 +1,83 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Threading.Tasks.Sources +{ + /// <summary> + /// Flags passed from <see cref="ValueTask"/> and <see cref="ValueTask{TResult}"/> to + /// <see cref="IValueTaskSource.OnCompleted"/> and <see cref="IValueTaskSource{TResult}.OnCompleted"/> + /// to control behavior. + /// </summary> + [Flags] + public enum ValueTaskSourceOnCompletedFlags + { + /// <summary> + /// No requirements are placed on how the continuation is invoked. + /// </summary> + None, + /// <summary> + /// Set if OnCompleted should capture the current scheduling context (e.g. SynchronizationContext) + /// and use it when queueing the continuation for execution. If this is not set, the implementation + /// may choose to execute the continuation in an arbitrary location. + /// </summary> + UseSchedulingContext = 0x1, + /// <summary> + /// Set if OnCompleted should capture the current <see cref="ExecutionContext"/> and use it to + /// <see cref="ExecutionContext.Run"/> the continuation. + /// </summary> + FlowExecutionContext = 0x2, + } + + /// <summary>Indicates the status of an <see cref="IValueTaskSource"/> or <see cref="IValueTaskSource{TResult}"/>.</summary> + public enum ValueTaskSourceStatus + { + /// <summary>The operation has not yet completed.</summary> + Pending = 0, + /// <summary>The operation completed successfully.</summary> + Succeeded = 1, + /// <summary>The operation completed with an error.</summary> + Faulted = 2, + /// <summary>The operation completed due to cancellation.</summary> + Canceled = 3 + } + + /// <summary>Represents an object that can be wrapped by a <see cref="ValueTask"/>.</summary> + public interface IValueTaskSource + { + /// <summary>Gets the status of the current operation.</summary> + /// <param name="token">Opaque value that was provided to the <see cref="ValueTask"/>'s constructor.</param> + ValueTaskSourceStatus GetStatus(short token); + + /// <summary>Schedules the continuation action for this <see cref="IValueTaskSource"/>.</summary> + /// <param name="continuation">The continuation to invoke when the operation has completed.</param> + /// <param name="state">The state object to pass to <paramref name="continuation"/> when it's invoked.</param> + /// <param name="token">Opaque value that was provided to the <see cref="ValueTask"/>'s constructor.</param> + /// <param name="flags">The flags describing the behavior of the continuation.</param> + void OnCompleted(Action<object> continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags); + + /// <summary>Gets the result of the <see cref="IValueTaskSource"/>.</summary> + /// <param name="token">Opaque value that was provided to the <see cref="ValueTask"/>'s constructor.</param> + void GetResult(short token); + } + + /// <summary>Represents an object that can be wrapped by a <see cref="ValueTask{TResult}"/>.</summary> + /// <typeparam name="TResult">Specifies the type of data returned from the object.</typeparam> + public interface IValueTaskSource<out TResult> + { + /// <summary>Gets the status of the current operation.</summary> + /// <param name="token">Opaque value that was provided to the <see cref="ValueTask"/>'s constructor.</param> + ValueTaskSourceStatus GetStatus(short token); + + /// <summary>Schedules the continuation action for this <see cref="IValueTaskSource{TResult}"/>.</summary> + /// <param name="continuation">The continuation to invoke when the operation has completed.</param> + /// <param name="state">The state object to pass to <paramref name="continuation"/> when it's invoked.</param> + /// <param name="token">Opaque value that was provided to the <see cref="ValueTask"/>'s constructor.</param> + /// <param name="flags">The flags describing the behavior of the continuation.</param> + void OnCompleted(Action<object> continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags); + + /// <summary>Gets the result of the <see cref="IValueTaskSource{TResult}"/>.</summary> + /// <param name="token">Opaque value that was provided to the <see cref="ValueTask"/>'s constructor.</param> + TResult GetResult(short token); + } +} 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 5edd8501b..6c45ed656 100644 --- a/src/System.Private.CoreLib/shared/System/Threading/Tasks/ValueTask.cs +++ b/src/System.Private.CoreLib/shared/System/Threading/Tasks/ValueTask.cs @@ -3,71 +3,415 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; +using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Threading.Tasks.Sources; + +#if !netstandard +using Internal.Runtime.CompilerServices; +#endif namespace System.Threading.Tasks { - /// <summary> - /// Provides a value type that wraps a <see cref="Task{TResult}"/> and a <typeparamref name="TResult"/>, - /// only one of which is used. - /// </summary> - /// <typeparam name="TResult">The type of the result.</typeparam> + /// <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. + /// </remarks> + [AsyncMethodBuilder(typeof(AsyncValueTaskMethodBuilder))] + [StructLayout(LayoutKind.Auto)] + public readonly struct ValueTask : IEquatable<ValueTask> + { +#if netstandard + /// <summary>A successfully completed task.</summary> + private static readonly Task s_completedTask = Task.Delay(0); +#endif + + /// <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; + + // An instance created with the default ctor (a zero init'd struct) represents a synchronously, successfully completed operation. + + /// <summary>Initialize the <see cref="ValueTask"/> with a <see cref="Task"/> that represents the operation.</summary> + /// <param name="task">The task.</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ValueTask(Task task) + { + if (task == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.task); + } + + _obj = task; + + _flags = ValueTaskFlags.ObjectIsTask; + _token = 0; + } + + /// <summary>Initialize the <see cref="ValueTask"/> with a <see cref="IValueTaskSource"/> object that represents the operation.</summary> + /// <param name="source">The source.</param> + /// <param name="token">Opaque value passed through to the <see cref="IValueTaskSource"/>.</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ValueTask(IValueTaskSource source, short token) + { + if (source == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); + } + + _obj = source; + _token = token; + + _flags = 0; + } + + /// <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) + { + _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> + internal Task UnsafeTask + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + 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> + internal IValueTaskSource UnsafeValueTaskSource + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + Debug.Assert(!ObjectIsTask); + Debug.Assert(_obj is IValueTaskSource); + return Unsafe.As<IValueTaskSource>(_obj); + } + } + + /// <summary>Returns the hash code for this instance.</summary> + public override int GetHashCode() => _obj?.GetHashCode() ?? 0; + + /// <summary>Returns a value indicating whether this value is equal to a specified <see cref="object"/>.</summary> + public override bool Equals(object obj) => + obj is ValueTask && + Equals((ValueTask)obj); + + /// <summary>Returns a value indicating whether this value is equal to a specified <see cref="ValueTask"/> value.</summary> + public bool Equals(ValueTask other) => _obj == other._obj && _token == other._token; + + /// <summary>Returns a value indicating whether two <see cref="ValueTask"/> values are equal.</summary> + public static bool operator ==(ValueTask left, ValueTask right) => + left.Equals(right); + + /// <summary>Returns a value indicating whether two <see cref="ValueTask"/> values are not equal.</summary> + public static bool operator !=(ValueTask left, ValueTask right) => + !left.Equals(right); + + /// <summary> + /// Gets a <see cref="Task"/> object to represent this ValueTask. + /// </summary> + /// <remarks> + /// 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 ? +#if netstandard + s_completedTask : +#else + Task.CompletedTask : +#endif + ObjectIsTask ? UnsafeTask : + GetTaskForValueTaskSource(); + + /// <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() + { + IValueTaskSource t = UnsafeValueTaskSource; + ValueTaskSourceStatus status = t.GetStatus(_token); + if (status != ValueTaskSourceStatus.Pending) + { + try + { + // Propagate any exceptions that may have occurred, then return + // an already successfully completed task. + t.GetResult(_token); + return +#if netstandard + s_completedTask; +#else + Task.CompletedTask; +#endif + + // If status is Faulted or Canceled, GetResult should throw. But + // we can't guarantee every implementation will do the "right thing". + // If it doesn't throw, we just treat that as success and ignore + // the status. + } + catch (Exception exc) + { + if (status == ValueTaskSourceStatus.Canceled) + { +#if netstandard + var tcs = new TaskCompletionSource<bool>(); + tcs.TrySetCanceled(); + return tcs.Task; +#else + 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 + } + else + { +#if netstandard + var tcs = new TaskCompletionSource<bool>(); + tcs.TrySetException(exc); + return tcs.Task; +#else + return Task.FromException(exc); +#endif + } + } + } + + var m = new ValueTaskSourceTask(t, _token); + return +#if netstandard + m.Task; +#else + m; +#endif + } + + /// <summary>Type used to create a <see cref="Task"/> to represent a <see cref="IValueTaskSource"/>.</summary> + private sealed class ValueTaskSourceTask : +#if netstandard + TaskCompletionSource<bool> +#else + Task<VoidTaskResult> +#endif + { + private static readonly Action<object> s_completionAction = state => + { + if (!(state is ValueTaskSourceTask vtst) || + !(vtst._source is IValueTaskSource source)) + { + // This could only happen if the IValueTaskSource passed the wrong state + // or if this callback were invoked multiple times such that the state + // was previously nulled out. + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.state); + return; + } + + vtst._source = null; + ValueTaskSourceStatus status = source.GetStatus(vtst._token); + try + { + source.GetResult(vtst._token); + vtst.TrySetResult(default); + } + catch (Exception exc) + { + if (status == ValueTaskSourceStatus.Canceled) + { +#if netstandard + vtst.TrySetCanceled(); +#else + if (exc is OperationCanceledException oce) + { + vtst.TrySetCanceled(oce.CancellationToken, oce); + } + else + { + vtst.TrySetCanceled(new CancellationToken(true)); + } +#endif + } + else + { + vtst.TrySetException(exc); + } + } + }; + + /// <summary>The associated <see cref="IValueTaskSource"/>.</summary> + private IValueTaskSource _source; + /// <summary>The token to pass through to operations on <see cref="_source"/></summary> + private readonly short _token; + + public ValueTaskSourceTask(IValueTaskSource source, short token) + { + _token = token; + _source = source; + source.OnCompleted(s_completionAction, this, token, ValueTaskSourceOnCompletedFlags.None); + } + } + + /// <summary>Gets whether the <see cref="ValueTask"/> represents a completed operation.</summary> + public bool IsCompleted + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _obj == null || (ObjectIsTask ? UnsafeTask.IsCompleted : UnsafeValueTaskSource.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 ? +#if netstandard + UnsafeTask.Status == TaskStatus.RanToCompletion : +#else + UnsafeTask.IsCompletedSuccessfully : +#endif + UnsafeValueTaskSource.GetStatus(_token) == ValueTaskSourceStatus.Succeeded); + } + + /// <summary>Gets whether the <see cref="ValueTask"/> represents a failed operation.</summary> + public bool IsFaulted + { + get => + _obj != null && + (ObjectIsTask ? UnsafeTask.IsFaulted : UnsafeValueTaskSource.GetStatus(_token) == ValueTaskSourceStatus.Faulted); + } + + /// <summary>Gets whether the <see cref="ValueTask"/> represents a canceled operation.</summary> + /// <remarks> + /// If the <see cref="ValueTask"/> is backed by a result or by a <see cref="IValueTaskSource"/>, + /// this will always return false. If it's backed by a <see cref="Task"/>, it'll return the + /// value of the task's <see cref="Task.IsCanceled"/> property. + /// </remarks> + public bool IsCanceled + { + get => + _obj != null && + (ObjectIsTask ? UnsafeTask.IsCanceled : UnsafeValueTaskSource.GetStatus(_token) == ValueTaskSourceStatus.Canceled); + } + + /// <summary>Throws the exception that caused the <see cref="ValueTask"/> to fail. If it completed successfully, nothing is thrown.</summary> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [StackTraceHidden] + internal void ThrowIfCompletedUnsuccessfully() + { + if (_obj != null) + { + if (ObjectIsTask) + { +#if netstandard + UnsafeTask.GetAwaiter().GetResult(); +#else + TaskAwaiter.ValidateEnd(UnsafeTask); +#endif + } + else + { + UnsafeValueTaskSource.GetResult(_token); + } + } + } + + /// <summary>Gets an awaiter for this <see cref="ValueTask"/>.</summary> + public ValueTaskAwaiter GetAwaiter() => new ValueTaskAwaiter(this); + + /// <summary>Configures an awaiter for this <see cref="ValueTask"/>.</summary> + /// <param name="continueOnCapturedContext"> + /// 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))); + } + } + + /// <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> - /// <para> - /// Methods may return an instance of this value type when it's likely that the result of their - /// operations will be available synchronously and when the method is expected to be invoked so - /// frequently that the cost of allocating a new <see cref="Task{TResult}"/> for each call will - /// be prohibitive. - /// </para> - /// <para> - /// There are tradeoffs to using a <see cref="ValueTask{TResult}"/> instead of a <see cref="Task{TResult}"/>. - /// For example, while a <see cref="ValueTask{TResult}"/> can help avoid an allocation in the case where the - /// successful result is available synchronously, it also contains two fields whereas a <see cref="Task{TResult}"/> - /// as a reference type is a single field. This means that a method call ends up returning two fields worth of - /// data instead of one, which is more data to copy. It also means that if a method that returns one of these - /// is awaited within an async method, the state machine for that async method will be larger due to needing - /// to store the struct that's two fields instead of a single reference. - /// </para> - /// <para> - /// Further, for uses other than consuming the result of an asynchronous operation via await, - /// <see cref="ValueTask{TResult}"/> can lead to a more convoluted programming model, which can in turn actually - /// lead to more allocations. For example, consider a method that could return either a <see cref="Task{TResult}"/> - /// with a cached task as a common result or a <see cref="ValueTask{TResult}"/>. If the consumer of the result - /// wants to use it as a <see cref="Task{TResult}"/>, such as to use with in methods like Task.WhenAll and Task.WhenAny, - /// the <see cref="ValueTask{TResult}"/> would first need to be converted into a <see cref="Task{TResult}"/> using - /// <see cref="ValueTask{TResult}.AsTask"/>, which leads to an allocation that would have been avoided if a cached - /// <see cref="Task{TResult}"/> had been used in the first place. - /// </para> - /// <para> - /// As such, the default choice for any asynchronous method should be to return a <see cref="Task"/> or - /// <see cref="Task{TResult}"/>. Only if performance analysis proves it worthwhile should a <see cref="ValueTask{TResult}"/> - /// be used instead of <see cref="Task{TResult}"/>. There is no non-generic version of <see cref="ValueTask{TResult}"/> - /// as the Task.CompletedTask property may be used to hand back a successfully completed singleton in the case where - /// a <see cref="Task"/>-returning method completes synchronously and successfully. - /// </para> + /// <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. /// </remarks> [AsyncMethodBuilder(typeof(AsyncValueTaskMethodBuilder<>))] [StructLayout(LayoutKind.Auto)] public readonly struct ValueTask<TResult> : IEquatable<ValueTask<TResult>> { - /// <summary>The task to be used if the operation completed asynchronously or if it completed synchronously but non-successfully.</summary> - internal readonly Task<TResult> _task; + /// <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; + + // An instance created with the default ctor (a zero init'd struct) represents a synchronously, successfully completed operation + // with a result of default(TResult). - /// <summary>Initialize the <see cref="ValueTask{TResult}"/> with the result of the successful operation.</summary> + /// <summary>Initialize the <see cref="ValueTask{TResult}"/> with a <typeparamref name="TResult"/> result value.</summary> /// <param name="result">The result.</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ValueTask(TResult result) { - _task = null; _result = result; + + _obj = null; + _flags = 0; + _token = 0; } - /// <summary> - /// Initialize the <see cref="ValueTask{TResult}"/> with a <see cref="Task{TResult}"/> that represents the operation. - /// </summary> + /// <summary>Initialize the <see cref="ValueTask{TResult}"/> with a <see cref="Task{TResult}"/> that represents the operation.</summary> /// <param name="task">The task.</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ValueTask(Task<TResult> task) { if (task == null) @@ -75,95 +419,341 @@ namespace System.Threading.Tasks ThrowHelper.ThrowArgumentNullException(ExceptionArgument.task); } - _task = task; + _obj = task; + + _result = default; + _flags = ValueTaskFlags.ObjectIsTask; + _token = 0; + } + + /// <summary>Initialize the <see cref="ValueTask{TResult}"/> with a <see cref="IValueTaskSource{TResult}"/> object that represents the operation.</summary> + /// <param name="source">The source.</param> + /// <param name="token">Opaque value passed through to the <see cref="IValueTaskSource"/>.</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ValueTask(IValueTaskSource<TResult> source, short token) + { + if (source == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); + } + + _obj = source; + _token = token; + _result = default; + _flags = 0; + } + + /// <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> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ValueTask(object obj, TResult result, short token, ValueTaskFlags flags) + { + _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; + } + + /// <summary>Returns the <see cref="Task{TResult}"/> stored in <see cref="_obj"/>. This uses <see cref="Unsafe"/>.</summary> + internal Task<TResult> UnsafeTask + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + 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> + internal IValueTaskSource<TResult> UnsafeValueTaskSource + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + 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() => - _task != null ? _task.GetHashCode() : + _obj != null ? _obj.GetHashCode() : _result != null ? _result.GetHashCode() : 0; /// <summary>Returns a value indicating whether this value is equal to a specified <see cref="object"/>.</summary> public override bool Equals(object obj) => - obj is ValueTask<TResult> && + obj is ValueTask<TResult> && Equals((ValueTask<TResult>)obj); /// <summary>Returns a value indicating whether this value is equal to a specified <see cref="ValueTask{TResult}"/> value.</summary> public bool Equals(ValueTask<TResult> other) => - _task != null || other._task != null ? - _task == other._task : + _obj != null || other._obj != null ? + _obj == other._obj && _token == other._token : EqualityComparer<TResult>.Default.Equals(_result, other._result); /// <summary>Returns a value indicating whether two <see cref="ValueTask{TResult}"/> values are equal.</summary> - public static bool operator==(ValueTask<TResult> left, ValueTask<TResult> right) => + public static bool operator ==(ValueTask<TResult> left, ValueTask<TResult> right) => left.Equals(right); /// <summary>Returns a value indicating whether two <see cref="ValueTask{TResult}"/> values are not equal.</summary> - public static bool operator!=(ValueTask<TResult> left, ValueTask<TResult> right) => + public static bool operator !=(ValueTask<TResult> left, ValueTask<TResult> right) => !left.Equals(right); /// <summary> - /// Gets a <see cref="Task{TResult}"/> object to represent this ValueTask. It will - /// either return the wrapped task object if one exists, or it'll manufacture a new - /// task object to represent the result. + /// Gets a <see cref="Task{TResult}"/> object to represent this ValueTask. /// </summary> + /// <remarks> + /// 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() => - // Return the task if we were constructed from one, otherwise manufacture one. We don't - // cache the generated task into _task as it would end up changing both equality comparison - // and the hash code we generate in GetHashCode. - _task ?? + _obj == null ? +#if netstandard + Task.FromResult(_result) : +#else + AsyncTaskMethodBuilder<TResult>.GetTaskForResult(_result) : +#endif + ObjectIsTask ? UnsafeTask : + GetTaskForValueTaskSource(); + + /// <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() + { + IValueTaskSource<TResult> t = UnsafeValueTaskSource; + ValueTaskSourceStatus status = t.GetStatus(_token); + if (status != ValueTaskSourceStatus.Pending) + { + try + { + // Get the result of the operation and return a task for it. + // If any exception occurred, propagate it + return +#if netstandard + Task.FromResult(t.GetResult(_token)); +#else + AsyncTaskMethodBuilder<TResult>.GetTaskForResult(t.GetResult(_token)); +#endif + + // If status is Faulted or Canceled, GetResult should throw. But + // we can't guarantee every implementation will do the "right thing". + // If it doesn't throw, we just treat that as success and ignore + // the status. + } + catch (Exception exc) + { + if (status == ValueTaskSourceStatus.Canceled) + { +#if netstandard + var tcs = new TaskCompletionSource<TResult>(); + tcs.TrySetCanceled(); + return tcs.Task; +#else + if (exc is OperationCanceledException oce) + { + var task = new Task<TResult>(); + task.TrySetCanceled(oce.CancellationToken, oce); + return task; + } + else + { + return Task.FromCanceled<TResult>(new CancellationToken(true)); + } +#endif + } + else + { #if netstandard - Task.FromResult(_result); + var tcs = new TaskCompletionSource<TResult>(); + tcs.TrySetException(exc); + return tcs.Task; #else - AsyncTaskMethodBuilder<TResult>.GetTaskForResult(_result); + return Task.FromException<TResult>(exc); #endif + } + } + } - internal Task<TResult> AsTaskExpectNonNull() => - // Return the task if we were constructed from one, otherwise manufacture one. - // Unlike AsTask(), this method is called only when we expect _task to be non-null, - // and thus we don't want GetTaskForResult inlined. - _task ?? GetTaskForResultNoInlining(); + var m = new ValueTaskSourceTask(t, _token); + return +#if netstandard + m.Task; +#else + m; +#endif + } - [MethodImpl(MethodImplOptions.NoInlining)] - private Task<TResult> GetTaskForResultNoInlining() => + /// <summary>Type used to create a <see cref="Task{TResult}"/> to represent a <see cref="IValueTaskSource{TResult}"/>.</summary> + private sealed class ValueTaskSourceTask : #if netstandard - Task.FromResult(_result); + TaskCompletionSource<TResult> #else - AsyncTaskMethodBuilder<TResult>.GetTaskForResult(_result); + Task<TResult> #endif + { + private static readonly Action<object> s_completionAction = state => + { + if (!(state is ValueTaskSourceTask vtst) || + !(vtst._source is IValueTaskSource<TResult> source)) + { + // This could only happen if the IValueTaskSource<TResult> passed the wrong state + // or if this callback were invoked multiple times such that the state + // was previously nulled out. + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.state); + return; + } + + vtst._source = null; + ValueTaskSourceStatus status = source.GetStatus(vtst._token); + try + { + vtst.TrySetResult(source.GetResult(vtst._token)); + } + catch (Exception exc) + { + if (status == ValueTaskSourceStatus.Canceled) + { +#if netstandard + vtst.TrySetCanceled(); +#else + if (exc is OperationCanceledException oce) + { + vtst.TrySetCanceled(oce.CancellationToken, oce); + } + else + { + vtst.TrySetCanceled(new CancellationToken(true)); + } +#endif + } + else + { + vtst.TrySetException(exc); + } + } + }; + + /// <summary>The associated <see cref="IValueTaskSource"/>.</summary> + private IValueTaskSource<TResult> _source; + /// <summary>The token to pass through to operations on <see cref="_source"/></summary> + private readonly short _token; + + public ValueTaskSourceTask(IValueTaskSource<TResult> source, short token) + { + _source = source; + _token = token; + source.OnCompleted(s_completionAction, this, token, ValueTaskSourceOnCompletedFlags.None); + } + } /// <summary>Gets whether the <see cref="ValueTask{TResult}"/> represents a completed operation.</summary> - public bool IsCompleted => _task == null || _task.IsCompleted; + public bool IsCompleted + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _obj == null || (ObjectIsTask ? UnsafeTask.IsCompleted : UnsafeValueTaskSource.GetStatus(_token) != ValueTaskSourceStatus.Pending); + } /// <summary>Gets whether the <see cref="ValueTask{TResult}"/> represents a successfully completed operation.</summary> - public bool IsCompletedSuccessfully => - _task == null || + public bool IsCompletedSuccessfully + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => + _obj == null || + (ObjectIsTask ? #if netstandard - _task.Status == TaskStatus.RanToCompletion; + UnsafeTask.Status == TaskStatus.RanToCompletion : #else - _task.IsCompletedSuccessfully; + UnsafeTask.IsCompletedSuccessfully : #endif + UnsafeValueTaskSource.GetStatus(_token) == ValueTaskSourceStatus.Succeeded); + } /// <summary>Gets whether the <see cref="ValueTask{TResult}"/> represents a failed operation.</summary> - public bool IsFaulted => _task != null && _task.IsFaulted; + public bool IsFaulted + { + get => + _obj != null && + (ObjectIsTask ? UnsafeTask.IsFaulted : UnsafeValueTaskSource.GetStatus(_token) == ValueTaskSourceStatus.Faulted); + } /// <summary>Gets whether the <see cref="ValueTask{TResult}"/> represents a canceled operation.</summary> - public bool IsCanceled => _task != null && _task.IsCanceled; + /// <remarks> + /// If the <see cref="ValueTask{TResult}"/> is backed by a result or by a <see cref="IValueTaskSource{TResult}"/>, + /// this will always return false. If it's backed by a <see cref="Task"/>, it'll return the + /// value of the task's <see cref="Task.IsCanceled"/> property. + /// </remarks> + public bool IsCanceled + { + get => + _obj != null && + (ObjectIsTask ? UnsafeTask.IsCanceled : UnsafeValueTaskSource.GetStatus(_token) == ValueTaskSourceStatus.Canceled); + } /// <summary>Gets the result.</summary> - public TResult Result => _task == null ? _result : _task.GetAwaiter().GetResult(); + public TResult Result + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if (_obj == null) + { + return _result; + } - /// <summary>Gets an awaiter for this value.</summary> + if (ObjectIsTask) + { +#if netstandard + return UnsafeTask.GetAwaiter().GetResult(); +#else + Task<TResult> t = UnsafeTask; + TaskAwaiter.ValidateEnd(t); + return t.ResultOnSuccess; +#endif + } + + return UnsafeValueTaskSource.GetResult(_token); + } + } + + /// <summary>Gets an awaiter for this <see cref="ValueTask{TResult}"/>.</summary> + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ValueTaskAwaiter<TResult> GetAwaiter() => new ValueTaskAwaiter<TResult>(this); - /// <summary>Configures an awaiter for this value.</summary> + /// <summary>Configures an awaiter for this <see cref="ValueTask{TResult}"/>.</summary> /// <param name="continueOnCapturedContext"> /// true to attempt to marshal the continuation back to the captured context; otherwise, false. /// </param> - public ConfiguredValueTaskAwaitable<TResult> ConfigureAwait(bool continueOnCapturedContext) => - new ConfiguredValueTaskAwaitable<TResult>(this, continueOnCapturedContext); + [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))); + } /// <summary>Gets a string-representation of this <see cref="ValueTask{TResult}"/>.</summary> public override string ToString() @@ -180,4 +770,26 @@ 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 + } } |