From f1e1cdd7605bf6037e4a7c3dfa83e22f9f9c8ffa Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Thu, 1 Mar 2018 06:14:38 -0500 Subject: [PATCH] Merge pull request dotnet/coreclr#16618 from stephentoub/valuetaskextensibility Implement ValueTask extensibility Signed-off-by: dotnet-bot-corefx-mirror --- .../System.Private.CoreLib.Shared.projitems | 1 + .../src/CoreLib/System/IO/FileStream.Unix.cs | 12 +- .../CoreLib/System/IO/FileStream.Windows.cs | 25 +- .../src/CoreLib/System/IO/FileStream.cs | 8 +- .../src/CoreLib/System/IO/MemoryStream.cs | 10 +- .../src/CoreLib/System/IO/StreamReader.cs | 8 +- .../src/CoreLib/System/IO/StreamWriter.cs | 4 +- .../System/IO/UnmanagedMemoryStream.cs | 8 +- .../System/IO/UnmanagedMemoryStreamWrapper.cs | 2 +- .../AsyncValueTaskMethodBuilder.cs | 104 ++- .../ConfiguredValueTaskAwaitable.cs | 218 +++-- .../CompilerServices/ValueTaskAwaiter.cs | 188 ++++- .../Tasks/Sources/IValueTaskSource.cs | 83 ++ .../System/Threading/Tasks/ValueTask.cs | 774 ++++++++++++++++-- 14 files changed, 1254 insertions(+), 191 deletions(-) create mode 100644 src/Common/src/CoreLib/System/Threading/Tasks/Sources/IValueTaskSource.cs diff --git a/src/Common/src/CoreLib/System.Private.CoreLib.Shared.projitems b/src/Common/src/CoreLib/System.Private.CoreLib.Shared.projitems index b864121125b8..fafe4b929222 100644 --- a/src/Common/src/CoreLib/System.Private.CoreLib.Shared.projitems +++ b/src/Common/src/CoreLib/System.Private.CoreLib.Shared.projitems @@ -551,6 +551,7 @@ + diff --git a/src/Common/src/CoreLib/System/IO/FileStream.Unix.cs b/src/Common/src/CoreLib/System/IO/FileStream.Unix.cs index 31b9ac53b486..d9fcf6571192 100644 --- a/src/Common/src/CoreLib/System/IO/FileStream.Unix.cs +++ b/src/Common/src/CoreLib/System/IO/FileStream.Unix.cs @@ -635,12 +635,12 @@ private unsafe void WriteNative(ReadOnlySpan source) /// The buffer to write data from. /// The token to monitor for cancellation requests. /// A task that represents the asynchronous write operation. - private Task WriteAsyncInternal(ReadOnlyMemory source, CancellationToken cancellationToken) + private ValueTask WriteAsyncInternal(ReadOnlyMemory 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 @@ private Task WriteAsyncInternal(ReadOnlyMemory source, CancellationToken c source.Span.CopyTo(new Span(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 @@ private Task WriteAsyncInternal(ReadOnlyMemory source, CancellationToken c // 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 @@ private Task WriteAsyncInternal(ReadOnlyMemory source, CancellationToken c thisRef.WriteSpan(readOnlyMemory.Span); } finally { thisRef._asyncState.Release(); } - }, this, CancellationToken.None, TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default); + }, this, CancellationToken.None, TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default)); } /// Sets the current position of this stream to the given value. diff --git a/src/Common/src/CoreLib/System/IO/FileStream.Windows.cs b/src/Common/src/CoreLib/System/IO/FileStream.Windows.cs index 85f045426dbd..291a30bb5385 100644 --- a/src/Common/src/CoreLib/System/IO/FileStream.Windows.cs +++ b/src/Common/src/CoreLib/System/IO/FileStream.Windows.cs @@ -961,7 +961,7 @@ unsafe private Task ReadNativeAsync(Memory destination, int numBuffer return completionSource.Task; } - private Task WriteAsyncInternal(ReadOnlyMemory source, CancellationToken cancellationToken) + private ValueTask WriteAsyncInternal(ReadOnlyMemory 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 @@ private Task WriteAsyncInternal(ReadOnlyMemory source, CancellationToken c // 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 @@ private Task WriteAsyncInternal(ReadOnlyMemory source, CancellationToken c flushTask.IsFaulted || flushTask.IsCanceled) { - return flushTask; + return new ValueTask(flushTask); } } @@ -1061,10 +1061,10 @@ private Task WriteAsyncInternal(ReadOnlyMemory source, CancellationToken c // 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 source, CancellationToken cancellationToken) @@ -1319,7 +1319,7 @@ private async Task AsyncModeCopyToAsync(Stream destination, int bufferSize, Canc int bufferedBytes = _readLength - _readPos; if (bufferedBytes > 0) { - await destination.WriteAsync(GetBuffer(), _readPos, bufferedBytes, cancellationToken).ConfigureAwait(false); + await destination.WriteAsync(new ReadOnlyMemory(GetBuffer(), _readPos, bufferedBytes), cancellationToken).ConfigureAwait(false); _readPos = _readLength = 0; } } @@ -1345,7 +1345,6 @@ private async Task AsyncModeCopyToAsync(Stream destination, int bufferSize, Canc // 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.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 @@ private async Task AsyncModeCopyToAsync(Stream destination, int bufferSize, Canc { 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 @@ private async Task AsyncModeCopyToAsync(Stream destination, int bufferSize, Canc } // Write out the read data. - await destination.WriteAsync(copyBuffer, 0, (int)readAwaitable._numBytes, cancellationToken).ConfigureAwait(false); + await destination.WriteAsync(new ReadOnlyMemory(copyBuffer, 0, (int)readAwaitable._numBytes), cancellationToken).ConfigureAwait(false); } } finally @@ -1488,8 +1480,7 @@ private async Task AsyncModeCopyToAsync(Stream destination, int bufferSize, Canc cancellationReg.Dispose(); awaitableOverlapped.Dispose(); - Array.Clear(copyBuffer, 0, bufferSize); - ArrayPool.Shared.Return(copyBuffer, clearArray: false); + ArrayPool.Shared.Return(copyBuffer); // Make sure the stream's current position reflects where we ended up if (!_fileHandle.IsClosed && CanSeek) diff --git a/src/Common/src/CoreLib/System/IO/FileStream.cs b/src/Common/src/CoreLib/System/IO/FileStream.cs index 4593768bd96f..717b73ff1339 100644 --- a/src/Common/src/CoreLib/System/IO/FileStream.cs +++ b/src/Common/src/CoreLib/System/IO/FileStream.cs @@ -458,10 +458,10 @@ public override Task WriteAsync(byte[] buffer, int offset, int count, Cancellati if (IsClosed) throw Error.GetFileNotOpen(); - return WriteAsyncInternal(new ReadOnlyMemory(buffer, offset, count), cancellationToken); + return WriteAsyncInternal(new ReadOnlyMemory(buffer, offset, count), cancellationToken).AsTask(); } - public override Task WriteAsync(ReadOnlyMemory source, CancellationToken cancellationToken = default(CancellationToken)) + public override ValueTask WriteAsync(ReadOnlyMemory source, CancellationToken cancellationToken = default(CancellationToken)) { if (!_useAsyncIO || GetType() != typeof(FileStream)) { @@ -473,7 +473,7 @@ public override Task WriteAsync(byte[] buffer, int offset, int count, Cancellati if (cancellationToken.IsCancellationRequested) { - return Task.FromCanceled(cancellationToken); + return new ValueTask(Task.FromCanceled(cancellationToken)); } if (IsClosed) @@ -853,7 +853,7 @@ public override IAsyncResult BeginWrite(byte[] array, int offset, int numBytes, if (!IsAsync) return base.BeginWrite(array, offset, numBytes, callback, state); else - return TaskToApm.Begin(WriteAsyncInternal(new ReadOnlyMemory(array, offset, numBytes), CancellationToken.None), callback, state); + return TaskToApm.Begin(WriteAsyncInternal(new ReadOnlyMemory(array, offset, numBytes), CancellationToken.None).AsTask(), callback, state); } public override int EndRead(IAsyncResult asyncResult) diff --git a/src/Common/src/CoreLib/System/IO/MemoryStream.cs b/src/Common/src/CoreLib/System/IO/MemoryStream.cs index c5e5ea918b44..8e573b749baf 100644 --- a/src/Common/src/CoreLib/System/IO/MemoryStream.cs +++ b/src/Common/src/CoreLib/System/IO/MemoryStream.cs @@ -752,11 +752,11 @@ public override Task WriteAsync(Byte[] buffer, int offset, int count, Cancellati } } - public override Task WriteAsync(ReadOnlyMemory source, CancellationToken cancellationToken = default(CancellationToken)) + public override ValueTask WriteAsync(ReadOnlyMemory source, CancellationToken cancellationToken = default(CancellationToken)) { if (cancellationToken.IsCancellationRequested) { - return Task.FromCanceled(cancellationToken); + return new ValueTask(Task.FromCanceled(cancellationToken)); } try @@ -771,15 +771,15 @@ public override Task WriteAsync(Byte[] buffer, int offset, int count, Cancellati { Write(source.Span); } - return Task.CompletedTask; + return default; } catch (OperationCanceledException oce) { - return Task.FromCancellation(oce); + return new ValueTask(Task.FromCancellation(oce)); } catch (Exception exception) { - return Task.FromException(exception); + return new ValueTask(Task.FromException(exception)); } } diff --git a/src/Common/src/CoreLib/System/IO/StreamReader.cs b/src/Common/src/CoreLib/System/IO/StreamReader.cs index 4e724ddb3004..22ec6e645bb6 100644 --- a/src/Common/src/CoreLib/System/IO/StreamReader.cs +++ b/src/Common/src/CoreLib/System/IO/StreamReader.cs @@ -1091,7 +1091,7 @@ internal override async ValueTask ReadAsyncInternal(Memory buffer, Ca { 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(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 @@ internal override async ValueTask ReadAsyncInternal(Memory buffer, Ca { 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(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 @@ private async Task ReadBufferAsync() { 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(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 @@ private async Task ReadBufferAsync() 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(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/Common/src/CoreLib/System/IO/StreamWriter.cs b/src/Common/src/CoreLib/System/IO/StreamWriter.cs index 6cdcd695244c..a37624428066 100644 --- a/src/Common/src/CoreLib/System/IO/StreamWriter.cs +++ b/src/Common/src/CoreLib/System/IO/StreamWriter.cs @@ -963,14 +963,14 @@ private static async Task FlushAsyncInternal(StreamWriter _this, bool flushStrea byte[] preamble = encoding.GetPreamble(); if (preamble.Length > 0) { - await stream.WriteAsync(preamble, 0, preamble.Length, cancellationToken).ConfigureAwait(false); + await stream.WriteAsync(new ReadOnlyMemory(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(byteBuffer, 0, count), cancellationToken).ConfigureAwait(false); } // By definition, calling Flush should flush the stream, but this is diff --git a/src/Common/src/CoreLib/System/IO/UnmanagedMemoryStream.cs b/src/Common/src/CoreLib/System/IO/UnmanagedMemoryStream.cs index 171113542f11..2f0f34afeea1 100644 --- a/src/Common/src/CoreLib/System/IO/UnmanagedMemoryStream.cs +++ b/src/Common/src/CoreLib/System/IO/UnmanagedMemoryStream.cs @@ -783,11 +783,11 @@ public override Task WriteAsync(Byte[] buffer, Int32 offset, Int32 count, Cancel /// /// Buffer that will be written. /// Token that can be used to cancel the operation. - public override Task WriteAsync(ReadOnlyMemory source, CancellationToken cancellationToken = default(CancellationToken)) + public override ValueTask WriteAsync(ReadOnlyMemory source, CancellationToken cancellationToken = default(CancellationToken)) { if (cancellationToken.IsCancellationRequested) { - return Task.FromCanceled(cancellationToken); + return new ValueTask(Task.FromCanceled(cancellationToken)); } try @@ -802,11 +802,11 @@ public override Task WriteAsync(Byte[] buffer, Int32 offset, Int32 count, Cancel { 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/Common/src/CoreLib/System/IO/UnmanagedMemoryStreamWrapper.cs b/src/Common/src/CoreLib/System/IO/UnmanagedMemoryStreamWrapper.cs index 90bb21ac5b28..f34c3c413791 100644 --- a/src/Common/src/CoreLib/System/IO/UnmanagedMemoryStreamWrapper.cs +++ b/src/Common/src/CoreLib/System/IO/UnmanagedMemoryStreamWrapper.cs @@ -217,7 +217,7 @@ public override Task WriteAsync(Byte[] buffer, Int32 offset, Int32 count, Cancel return _unmanagedStream.WriteAsync(buffer, offset, count, cancellationToken); } - public override Task WriteAsync(ReadOnlyMemory source, CancellationToken cancellationToken = default(CancellationToken)) + public override ValueTask WriteAsync(ReadOnlyMemory source, CancellationToken cancellationToken = default(CancellationToken)) { return _unmanagedStream.WriteAsync(source, cancellationToken); } diff --git a/src/Common/src/CoreLib/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs b/src/Common/src/CoreLib/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs index 49cdaccb0e4b..0e1220d11900 100644 --- a/src/Common/src/CoreLib/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs +++ b/src/Common/src/CoreLib/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs @@ -8,6 +8,108 @@ namespace System.Runtime.CompilerServices { + /// Represents a builder for asynchronous methods that return a . + [StructLayout(LayoutKind.Auto)] + public struct AsyncValueTaskMethodBuilder + { + /// The to which most operations are delegated. + private AsyncTaskMethodBuilder _methodBuilder; // mutable struct; do not make it readonly + /// true if completed synchronously and successfully; otherwise, false. + private bool _haveResult; + /// true if the builder should be used for setting/getting the result; otherwise, false. + private bool _useBuilder; + + /// Creates an instance of the struct. + /// The initialized instance. + 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 + + /// Begins running the builder with the associated state machine. + /// The type of the state machine. + /// The state machine instance, passed by reference. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Start(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine => + // will provide the right ExecutionContext semantics +#if netstandard + _methodBuilder.Start(ref stateMachine); +#else + AsyncMethodBuilderCore.Start(ref stateMachine); +#endif + + /// Associates the builder with the specified state machine. + /// The state machine instance to associate with the builder. + public void SetStateMachine(IAsyncStateMachine stateMachine) => _methodBuilder.SetStateMachine(stateMachine); + + /// Marks the task as successfully completed. + public void SetResult() + { + if (_useBuilder) + { + _methodBuilder.SetResult(); + } + else + { + _haveResult = true; + } + } + + /// Marks the task as failed and binds the specified exception to the task. + /// The exception to bind to the task. + public void SetException(Exception exception) => _methodBuilder.SetException(exception); + + /// Gets the task for this builder. + public ValueTask Task + { + get + { + if (_haveResult) + { + return default; + } + else + { + _useBuilder = true; + return new ValueTask(_methodBuilder.Task); + } + } + } + + /// Schedules the state machine to proceed to the next action when the specified awaiter completes. + /// The type of the awaiter. + /// The type of the state machine. + /// The awaiter. + /// The state machine. + public void AwaitOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) + where TAwaiter : INotifyCompletion + where TStateMachine : IAsyncStateMachine + { + _useBuilder = true; + _methodBuilder.AwaitOnCompleted(ref awaiter, ref stateMachine); + } + + /// Schedules the state machine to proceed to the next action when the specified awaiter completes. + /// The type of the awaiter. + /// The type of the state machine. + /// The awaiter. + /// The state machine. + [SecuritySafeCritical] + public void AwaitUnsafeOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) + where TAwaiter : ICriticalNotifyCompletion + where TStateMachine : IAsyncStateMachine + { + _useBuilder = true; + _methodBuilder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); + } + } + /// Represents a builder for asynchronous methods that returns a . /// The type of the result. [StructLayout(LayoutKind.Auto)] @@ -32,7 +134,7 @@ public static AsyncValueTaskMethodBuilder 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(AsyncValueTaskMethodBuilder); + default; #endif /// Begins running the builder with the associated state machine. diff --git a/src/Common/src/CoreLib/System/Runtime/CompilerServices/ConfiguredValueTaskAwaitable.cs b/src/Common/src/CoreLib/System/Runtime/CompilerServices/ConfiguredValueTaskAwaitable.cs index f22b9d94bf65..65d3d5670d86 100644 --- a/src/Common/src/CoreLib/System/Runtime/CompilerServices/ConfiguredValueTaskAwaitable.cs +++ b/src/Common/src/CoreLib/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 { + /// Provides an awaitable type that enables configured awaits on a . + [StructLayout(LayoutKind.Auto)] + public readonly struct ConfiguredValueTaskAwaitable + { + /// The wrapped . + private readonly ValueTask _value; + + /// Initializes the awaitable. + /// The wrapped . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ConfiguredValueTaskAwaitable(ValueTask value) => _value = value; + + /// Returns an awaiter for this instance. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ConfiguredValueTaskAwaiter GetAwaiter() => new ConfiguredValueTaskAwaiter(_value); + + /// Provides an awaiter for a . + [StructLayout(LayoutKind.Auto)] + public readonly struct ConfiguredValueTaskAwaiter : ICriticalNotifyCompletion +#if CORECLR + , IValueTaskAwaiter +#endif + { + /// The value being awaited. + private readonly ValueTask _value; + + /// Initializes the awaiter. + /// The value to be awaited. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ConfiguredValueTaskAwaiter(ValueTask value) => _value = value; + + /// Gets whether the has completed. + public bool IsCompleted + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _value.IsCompleted; + } + + /// Gets the result of the ValueTask. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [StackTraceHidden] + public void GetResult() => _value.ThrowIfCompletedUnsuccessfully(); + + /// Schedules the continuation action for the . + 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); + } + } + + /// Schedules the continuation action for the . + 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 + } + } + /// Provides an awaitable type that enables configured awaits on a . /// The type of the result produced. [StructLayout(LayoutKind.Auto)] @@ -15,78 +121,98 @@ public readonly struct ConfiguredValueTaskAwaitable { /// The wrapped . private readonly ValueTask _value; - /// true to attempt to marshal the continuation back to the original context captured; otherwise, false. - private readonly bool _continueOnCapturedContext; /// Initializes the awaitable. /// The wrapped . - /// - /// true to attempt to marshal the continuation back to the original synchronization context captured; otherwise, false. - /// - internal ConfiguredValueTaskAwaitable(ValueTask value, bool continueOnCapturedContext) - { - _value = value; - _continueOnCapturedContext = continueOnCapturedContext; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ConfiguredValueTaskAwaitable(ValueTask value) => _value = value; /// Returns an awaiter for this instance. - public ConfiguredValueTaskAwaiter GetAwaiter() => - new ConfiguredValueTaskAwaiter(_value, _continueOnCapturedContext); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ConfiguredValueTaskAwaiter GetAwaiter() => new ConfiguredValueTaskAwaiter(_value); /// Provides an awaiter for a . [StructLayout(LayoutKind.Auto)] - public readonly struct ConfiguredValueTaskAwaiter : ICriticalNotifyCompletion, IConfiguredValueTaskAwaiter + public readonly struct ConfiguredValueTaskAwaiter : ICriticalNotifyCompletion +#if CORECLR + , IValueTaskAwaiter +#endif { /// The value being awaited. private readonly ValueTask _value; - /// The value to pass to ConfigureAwait. - internal readonly bool _continueOnCapturedContext; /// Initializes the awaiter. /// The value to be awaited. - /// The value to pass to ConfigureAwait. - internal ConfiguredValueTaskAwaiter(ValueTask value, bool continueOnCapturedContext) - { - _value = value; - _continueOnCapturedContext = continueOnCapturedContext; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ConfiguredValueTaskAwaiter(ValueTask value) => _value = value; /// Gets whether the has completed. - public bool IsCompleted => _value.IsCompleted; + public bool IsCompleted + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _value.IsCompleted; + } /// Gets the result of the ValueTask. + [MethodImpl(MethodImplOptions.AggressiveInlining)] [StackTraceHidden] - public TResult GetResult() => - _value._task == null ? - _value._result : - _value._task.GetAwaiter().GetResult(); + public TResult GetResult() => _value.Result; /// Schedules the continuation action for the . - 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); + } + } /// Schedules the continuation action for the . - public void UnsafeOnCompleted(Action continuation) => - _value.AsTask().ConfigureAwait(_continueOnCapturedContext).GetAwaiter().UnsafeOnCompleted(continuation); - - /// Gets the task underlying . - internal Task 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); + } + } - /// Gets the task underlying the incomplete . - /// This method is used when awaiting and IsCompleted returned false; thus we expect the value task to be wrapping a non-null task. - 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 } } - - /// - /// Internal interface used to enable extract the Task from arbitrary configured ValueTask awaiters. - /// - internal interface IConfiguredValueTaskAwaiter - { - Task GetTask(out bool continueOnCapturedContext); - } } diff --git a/src/Common/src/CoreLib/System/Runtime/CompilerServices/ValueTaskAwaiter.cs b/src/Common/src/CoreLib/System/Runtime/CompilerServices/ValueTaskAwaiter.cs index 3f212d8bf9b8..0414a05a0dfb 100644 --- a/src/Common/src/CoreLib/System/Runtime/CompilerServices/ValueTaskAwaiter.cs +++ b/src/Common/src/CoreLib/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 { + /// Provides an awaiter for a . + public readonly struct ValueTaskAwaiter : ICriticalNotifyCompletion +#if CORECLR + , IValueTaskAwaiter +#endif + { + /// Shim used to invoke an passed as the state argument to a . + internal static readonly Action s_invokeActionDelegate = state => + { + if (!(state is Action action)) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.state); + return; + } + + action(); + }; + /// The value being awaited. + private readonly ValueTask _value; + + /// Initializes the awaiter. + /// The value to be awaited. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ValueTaskAwaiter(ValueTask value) => _value = value; + + /// Gets whether the has completed. + public bool IsCompleted + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _value.IsCompleted; + } + + /// Gets the result of the ValueTask. + [StackTraceHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void GetResult() => _value.ThrowIfCompletedUnsuccessfully(); + + /// Schedules the continuation action for this ValueTask. + 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); + } + } + + /// Schedules the continuation action for this ValueTask. + 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); + } + } + + /// Shim used to invoke of the supplied . + internal static readonly Action s_invokeAsyncStateMachineBox = state => + { + if (!(state is IAsyncStateMachineBox box)) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.state); + return; + } + + box.Invoke(null); + }; +#endif + } + /// Provides an awaiter for a . - public readonly struct ValueTaskAwaiter : ICriticalNotifyCompletion, IValueTaskAwaiter + public readonly struct ValueTaskAwaiter : ICriticalNotifyCompletion +#if CORECLR + , IValueTaskAwaiter +#endif { /// The value being awaited. private readonly ValueTask _value; /// Initializes the awaiter. /// The value to be awaited. + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal ValueTaskAwaiter(ValueTask value) => _value = value; /// Gets whether the has completed. - public bool IsCompleted => _value.IsCompleted; + public bool IsCompleted + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _value.IsCompleted; + } /// Gets the result of the ValueTask. [StackTraceHidden] - public TResult GetResult() => - _value._task == null ? - _value._result : - _value._task.GetAwaiter().GetResult(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public TResult GetResult() => _value.Result; /// Schedules the continuation action for this ValueTask. - 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); + } + } /// Schedules the continuation action for this ValueTask. - public void UnsafeOnCompleted(Action continuation) => - _value.AsTask().ConfigureAwait(continueOnCapturedContext: true).GetAwaiter().UnsafeOnCompleted(continuation); - - /// Gets the task underlying . - internal Task 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); + } + } - /// Gets the task underlying the incomplete . - /// This method is used when awaiting and IsCompleted returned false; thus we expect the value task to be wrapping a non-null task. - 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 } - /// - /// Internal interface used to enable extract the Task from arbitrary ValueTask awaiters. - /// > +#if CORECLR + /// Internal interface used to enable optimizations from on .> internal interface IValueTaskAwaiter { - Task GetTask(); + /// Invoked to set of the as the awaiter's continuation. + /// The box object. + void AwaitUnsafeOnCompleted(IAsyncStateMachineBox box); } +#endif } diff --git a/src/Common/src/CoreLib/System/Threading/Tasks/Sources/IValueTaskSource.cs b/src/Common/src/CoreLib/System/Threading/Tasks/Sources/IValueTaskSource.cs new file mode 100644 index 000000000000..3c1e8830a10d --- /dev/null +++ b/src/Common/src/CoreLib/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 +{ + /// + /// Flags passed from and to + /// and + /// to control behavior. + /// + [Flags] + public enum ValueTaskSourceOnCompletedFlags + { + /// + /// No requirements are placed on how the continuation is invoked. + /// + None, + /// + /// 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. + /// + UseSchedulingContext = 0x1, + /// + /// Set if OnCompleted should capture the current and use it to + /// the continuation. + /// + FlowExecutionContext = 0x2, + } + + /// Indicates the status of an or . + public enum ValueTaskSourceStatus + { + /// The operation has not yet completed. + Pending = 0, + /// The operation completed successfully. + Succeeded = 1, + /// The operation completed with an error. + Faulted = 2, + /// The operation completed due to cancellation. + Canceled = 3 + } + + /// Represents an object that can be wrapped by a . + public interface IValueTaskSource + { + /// Gets the status of the current operation. + /// Opaque value that was provided to the 's constructor. + ValueTaskSourceStatus GetStatus(short token); + + /// Schedules the continuation action for this . + /// The continuation to invoke when the operation has completed. + /// The state object to pass to when it's invoked. + /// Opaque value that was provided to the 's constructor. + /// The flags describing the behavior of the continuation. + void OnCompleted(Action continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags); + + /// Gets the result of the . + /// Opaque value that was provided to the 's constructor. + void GetResult(short token); + } + + /// Represents an object that can be wrapped by a . + /// Specifies the type of data returned from the object. + public interface IValueTaskSource + { + /// Gets the status of the current operation. + /// Opaque value that was provided to the 's constructor. + ValueTaskSourceStatus GetStatus(short token); + + /// Schedules the continuation action for this . + /// The continuation to invoke when the operation has completed. + /// The state object to pass to when it's invoked. + /// Opaque value that was provided to the 's constructor. + /// The flags describing the behavior of the continuation. + void OnCompleted(Action continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags); + + /// Gets the result of the . + /// Opaque value that was provided to the 's constructor. + TResult GetResult(short token); + } +} diff --git a/src/Common/src/CoreLib/System/Threading/Tasks/ValueTask.cs b/src/Common/src/CoreLib/System/Threading/Tasks/ValueTask.cs index 5edd8501b0c6..6c45ed65681e 100644 --- a/src/Common/src/CoreLib/System/Threading/Tasks/ValueTask.cs +++ b/src/Common/src/CoreLib/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 { - /// - /// Provides a value type that wraps a and a , - /// only one of which is used. - /// - /// The type of the result. + /// Provides an awaitable result of an asynchronous operation. + /// + /// s are meant to be directly awaited. To do more complicated operations with them, a + /// should be extracted using . 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. + /// + [AsyncMethodBuilder(typeof(AsyncValueTaskMethodBuilder))] + [StructLayout(LayoutKind.Auto)] + public readonly struct ValueTask : IEquatable + { +#if netstandard + /// A successfully completed task. + private static readonly Task s_completedTask = Task.Delay(0); +#endif + + /// null if representing a successful synchronous completion, otherwise a or a . + internal readonly object _obj; + /// Flags providing additional details about the ValueTask's contents and behavior. + internal readonly ValueTaskFlags _flags; + /// Opaque value passed through to the . + internal readonly short _token; + + // An instance created with the default ctor (a zero init'd struct) represents a synchronously, successfully completed operation. + + /// Initialize the with a that represents the operation. + /// The task. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ValueTask(Task task) + { + if (task == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.task); + } + + _obj = task; + + _flags = ValueTaskFlags.ObjectIsTask; + _token = 0; + } + + /// Initialize the with a object that represents the operation. + /// The source. + /// Opaque value passed through to the . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ValueTask(IValueTaskSource source, short token) + { + if (source == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); + } + + _obj = source; + _token = token; + + _flags = 0; + } + + /// Non-verified initialization of the struct to the specified values. + /// The object. + /// The token. + /// The flags. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ValueTask(object obj, short token, ValueTaskFlags flags) + { + _obj = obj; + _token = token; + _flags = flags; + } + + /// Gets whether the contination should be scheduled to the current context. + internal bool ContinueOnCapturedContext + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => (_flags & ValueTaskFlags.AvoidCapturedContext) == 0; + } + + /// Gets whether the object in the field is a . + internal bool ObjectIsTask + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => (_flags & ValueTaskFlags.ObjectIsTask) != 0; + } + + /// Returns the stored in . This uses . + internal Task UnsafeTask + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + Debug.Assert(ObjectIsTask); + Debug.Assert(_obj is Task); + return Unsafe.As(_obj); + } + } + + /// Returns the stored in . This uses . + internal IValueTaskSource UnsafeValueTaskSource + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + Debug.Assert(!ObjectIsTask); + Debug.Assert(_obj is IValueTaskSource); + return Unsafe.As(_obj); + } + } + + /// Returns the hash code for this instance. + public override int GetHashCode() => _obj?.GetHashCode() ?? 0; + + /// Returns a value indicating whether this value is equal to a specified . + public override bool Equals(object obj) => + obj is ValueTask && + Equals((ValueTask)obj); + + /// Returns a value indicating whether this value is equal to a specified value. + public bool Equals(ValueTask other) => _obj == other._obj && _token == other._token; + + /// Returns a value indicating whether two values are equal. + public static bool operator ==(ValueTask left, ValueTask right) => + left.Equals(right); + + /// Returns a value indicating whether two values are not equal. + public static bool operator !=(ValueTask left, ValueTask right) => + !left.Equals(right); + + /// + /// Gets a 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. + /// + public Task AsTask() => + _obj == null ? +#if netstandard + s_completedTask : +#else + Task.CompletedTask : +#endif + ObjectIsTask ? UnsafeTask : + GetTaskForValueTaskSource(); + + /// Gets a that may be used at any point in the future. + public ValueTask Preserve() => _obj == null ? this : new ValueTask(AsTask()); + + /// Creates a to represent the . + 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(); + tcs.TrySetCanceled(); + return tcs.Task; +#else + if (exc is OperationCanceledException oce) + { + var task = new Task(); + task.TrySetCanceled(oce.CancellationToken, oce); + return task; + } + else + { + return Task.FromCanceled(new CancellationToken(true)); + } +#endif + } + else + { +#if netstandard + var tcs = new TaskCompletionSource(); + 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 + } + + /// Type used to create a to represent a . + private sealed class ValueTaskSourceTask : +#if netstandard + TaskCompletionSource +#else + Task +#endif + { + private static readonly Action 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); + } + } + }; + + /// The associated . + private IValueTaskSource _source; + /// The token to pass through to operations on + private readonly short _token; + + public ValueTaskSourceTask(IValueTaskSource source, short token) + { + _token = token; + _source = source; + source.OnCompleted(s_completionAction, this, token, ValueTaskSourceOnCompletedFlags.None); + } + } + + /// Gets whether the represents a completed operation. + public bool IsCompleted + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _obj == null || (ObjectIsTask ? UnsafeTask.IsCompleted : UnsafeValueTaskSource.GetStatus(_token) != ValueTaskSourceStatus.Pending); + } + + /// Gets whether the represents a successfully completed operation. + 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); + } + + /// Gets whether the represents a failed operation. + public bool IsFaulted + { + get => + _obj != null && + (ObjectIsTask ? UnsafeTask.IsFaulted : UnsafeValueTaskSource.GetStatus(_token) == ValueTaskSourceStatus.Faulted); + } + + /// Gets whether the represents a canceled operation. + /// + /// If the is backed by a result or by a , + /// this will always return false. If it's backed by a , it'll return the + /// value of the task's property. + /// + public bool IsCanceled + { + get => + _obj != null && + (ObjectIsTask ? UnsafeTask.IsCanceled : UnsafeValueTaskSource.GetStatus(_token) == ValueTaskSourceStatus.Canceled); + } + + /// Throws the exception that caused the to fail. If it completed successfully, nothing is thrown. + [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); + } + } + } + + /// Gets an awaiter for this . + public ValueTaskAwaiter GetAwaiter() => new ValueTaskAwaiter(this); + + /// Configures an awaiter for this . + /// + /// true to attempt to marshal the continuation back to the captured context; otherwise, false. + /// + [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(ref avoidCapture))); + } + } + + /// Provides a value type that can represent a synchronously available value or a task object. + /// Specifies the type of the result. /// - /// - /// 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 for each call will - /// be prohibitive. - /// - /// - /// There are tradeoffs to using a instead of a . - /// For example, while a can help avoid an allocation in the case where the - /// successful result is available synchronously, it also contains two fields whereas a - /// 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. - /// - /// - /// Further, for uses other than consuming the result of an asynchronous operation via await, - /// 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 - /// with a cached task as a common result or a . If the consumer of the result - /// wants to use it as a , such as to use with in methods like Task.WhenAll and Task.WhenAny, - /// the would first need to be converted into a using - /// , which leads to an allocation that would have been avoided if a cached - /// had been used in the first place. - /// - /// - /// As such, the default choice for any asynchronous method should be to return a or - /// . Only if performance analysis proves it worthwhile should a - /// be used instead of . There is no non-generic version of - /// as the Task.CompletedTask property may be used to hand back a successfully completed singleton in the case where - /// a -returning method completes synchronously and successfully. - /// + /// s are meant to be directly awaited. To do more complicated operations with them, a + /// should be extracted using or . 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. /// [AsyncMethodBuilder(typeof(AsyncValueTaskMethodBuilder<>))] [StructLayout(LayoutKind.Auto)] public readonly struct ValueTask : IEquatable> { - /// The task to be used if the operation completed asynchronously or if it completed synchronously but non-successfully. - internal readonly Task _task; + /// null if has the result, otherwise a or a . + internal readonly object _obj; /// The result to be used if the operation completed successfully synchronously. internal readonly TResult _result; + /// Flags providing additional details about the ValueTask's contents and behavior. + internal readonly ValueTaskFlags _flags; + /// Opaque value passed through to the . + 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). - /// Initialize the with the result of the successful operation. + /// Initialize the with a result value. /// The result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ValueTask(TResult result) { - _task = null; _result = result; + + _obj = null; + _flags = 0; + _token = 0; } - /// - /// Initialize the with a that represents the operation. - /// + /// Initialize the with a that represents the operation. /// The task. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ValueTask(Task task) { if (task == null) @@ -75,95 +419,341 @@ public ValueTask(Task task) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.task); } - _task = task; + _obj = task; + + _result = default; + _flags = ValueTaskFlags.ObjectIsTask; + _token = 0; + } + + /// Initialize the with a object that represents the operation. + /// The source. + /// Opaque value passed through to the . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ValueTask(IValueTaskSource source, short token) + { + if (source == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); + } + + _obj = source; + _token = token; + _result = default; + _flags = 0; + } + + /// Non-verified initialization of the struct to the specified values. + /// The object. + /// The result. + /// The token. + /// The flags. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ValueTask(object obj, TResult result, short token, ValueTaskFlags flags) + { + _obj = obj; + _result = result; + _token = token; + _flags = flags; + } + + /// Gets whether the contination should be scheduled to the current context. + internal bool ContinueOnCapturedContext + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => (_flags & ValueTaskFlags.AvoidCapturedContext) == 0; + } + + /// Gets whether the object in the field is a . + internal bool ObjectIsTask + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => (_flags & ValueTaskFlags.ObjectIsTask) != 0; + } + + /// Returns the stored in . This uses . + internal Task UnsafeTask + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + Debug.Assert(ObjectIsTask); + Debug.Assert(_obj is Task); + return Unsafe.As>(_obj); + } + } + + /// Returns the stored in . This uses . + internal IValueTaskSource UnsafeValueTaskSource + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + Debug.Assert(!ObjectIsTask); + Debug.Assert(_obj is IValueTaskSource); + return Unsafe.As>(_obj); + } } /// Returns the hash code for this instance. public override int GetHashCode() => - _task != null ? _task.GetHashCode() : + _obj != null ? _obj.GetHashCode() : _result != null ? _result.GetHashCode() : 0; /// Returns a value indicating whether this value is equal to a specified . public override bool Equals(object obj) => - obj is ValueTask && + obj is ValueTask && Equals((ValueTask)obj); /// Returns a value indicating whether this value is equal to a specified value. public bool Equals(ValueTask other) => - _task != null || other._task != null ? - _task == other._task : + _obj != null || other._obj != null ? + _obj == other._obj && _token == other._token : EqualityComparer.Default.Equals(_result, other._result); /// Returns a value indicating whether two values are equal. - public static bool operator==(ValueTask left, ValueTask right) => + public static bool operator ==(ValueTask left, ValueTask right) => left.Equals(right); /// Returns a value indicating whether two values are not equal. - public static bool operator!=(ValueTask left, ValueTask right) => + public static bool operator !=(ValueTask left, ValueTask right) => !left.Equals(right); /// - /// Gets a 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 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. + /// public Task 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.GetTaskForResult(_result) : +#endif + ObjectIsTask ? UnsafeTask : + GetTaskForValueTaskSource(); + + /// Gets a that may be used at any point in the future. + public ValueTask Preserve() => _obj == null ? this : new ValueTask(AsTask()); + + /// Creates a to represent the . + private Task GetTaskForValueTaskSource() + { + IValueTaskSource 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.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(); + tcs.TrySetCanceled(); + return tcs.Task; +#else + if (exc is OperationCanceledException oce) + { + var task = new Task(); + task.TrySetCanceled(oce.CancellationToken, oce); + return task; + } + else + { + return Task.FromCanceled(new CancellationToken(true)); + } +#endif + } + else + { #if netstandard - Task.FromResult(_result); + var tcs = new TaskCompletionSource(); + tcs.TrySetException(exc); + return tcs.Task; #else - AsyncTaskMethodBuilder.GetTaskForResult(_result); + return Task.FromException(exc); #endif + } + } + } - internal Task 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 GetTaskForResultNoInlining() => + /// Type used to create a to represent a . + private sealed class ValueTaskSourceTask : #if netstandard - Task.FromResult(_result); + TaskCompletionSource #else - AsyncTaskMethodBuilder.GetTaskForResult(_result); + Task #endif + { + private static readonly Action 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 + { + 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); + } + } + }; + + /// The associated . + private IValueTaskSource _source; + /// The token to pass through to operations on + private readonly short _token; + + public ValueTaskSourceTask(IValueTaskSource source, short token) + { + _source = source; + _token = token; + source.OnCompleted(s_completionAction, this, token, ValueTaskSourceOnCompletedFlags.None); + } + } /// Gets whether the represents a completed operation. - public bool IsCompleted => _task == null || _task.IsCompleted; + public bool IsCompleted + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _obj == null || (ObjectIsTask ? UnsafeTask.IsCompleted : UnsafeValueTaskSource.GetStatus(_token) != ValueTaskSourceStatus.Pending); + } /// Gets whether the represents a successfully completed operation. - 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); + } /// Gets whether the represents a failed operation. - public bool IsFaulted => _task != null && _task.IsFaulted; + public bool IsFaulted + { + get => + _obj != null && + (ObjectIsTask ? UnsafeTask.IsFaulted : UnsafeValueTaskSource.GetStatus(_token) == ValueTaskSourceStatus.Faulted); + } /// Gets whether the represents a canceled operation. - public bool IsCanceled => _task != null && _task.IsCanceled; + /// + /// If the is backed by a result or by a , + /// this will always return false. If it's backed by a , it'll return the + /// value of the task's property. + /// + public bool IsCanceled + { + get => + _obj != null && + (ObjectIsTask ? UnsafeTask.IsCanceled : UnsafeValueTaskSource.GetStatus(_token) == ValueTaskSourceStatus.Canceled); + } /// Gets the result. - public TResult Result => _task == null ? _result : _task.GetAwaiter().GetResult(); + public TResult Result + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if (_obj == null) + { + return _result; + } - /// Gets an awaiter for this value. + if (ObjectIsTask) + { +#if netstandard + return UnsafeTask.GetAwaiter().GetResult(); +#else + Task t = UnsafeTask; + TaskAwaiter.ValidateEnd(t); + return t.ResultOnSuccess; +#endif + } + + return UnsafeValueTaskSource.GetResult(_token); + } + } + + /// Gets an awaiter for this . + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ValueTaskAwaiter GetAwaiter() => new ValueTaskAwaiter(this); - /// Configures an awaiter for this value. + /// Configures an awaiter for this . /// /// true to attempt to marshal the continuation back to the captured context; otherwise, false. /// - public ConfiguredValueTaskAwaitable ConfigureAwait(bool continueOnCapturedContext) => - new ConfiguredValueTaskAwaitable(this, continueOnCapturedContext); + [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, _result, _token, _flags | Unsafe.As(ref avoidCapture))); + } /// Gets a string-representation of this . public override string ToString() @@ -180,4 +770,26 @@ public override string ToString() return string.Empty; } } + + /// Internal flags used in the implementation of and . + [Flags] + internal enum ValueTaskFlags : byte + { + /// + /// Indicates that context (e.g. SynchronizationContext) should not be captured when adding + /// a continuation. + /// + /// + /// 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. + /// + AvoidCapturedContext = 0x1, + + /// + /// 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. + /// + ObjectIsTask = 0x2 + } }