From 1b81f5b28891e6431a29da53841082a0496ef4c0 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Wed, 28 Feb 2018 00:53:03 -0500 Subject: [PATCH 1/3] Implement ValueTask extensibility This commit adds support for extending `ValueTask` with arbitrary backing sources. Prior to this change, `ValueTask` could wrap a `T` or a `Task`; now it can also wrap an `IValueTaskSource`, which can be implemented by arbitrary objects to be represented by `ValueTask`. These objects can then be pooled and reused to minimize allocation. The commit also adds a non-generic `ValueTask` that can represent void-returning operations, including a `default` synchronous success, `Task`, and `IValueTaskSource`. For the non-generic `ValueTask`, the commit also includes awaiters and async method builders, so it can be both awaited and used as the return type of an async method. The rest of the changes fall into a few buckets all related to enabling this support: - Modifying `AsyncTaskMethodBuilder.AwaitUnsafeOnCompleted` to specially recognize any `ValueTask` and utilize either the `Task` or `IValueTaskSource` that backs it to avoid allocating an Action MoveNext method. If every object awaited in an async method is either a `Task`/`Task` or `ValueTask`/`ValueTask`, regardless of what the `ValueTask`/`ValueTask` wraps, we'll be able to avoid allocating the delegate and only allocate the single state machine object that also serves as the returned object. - Changing `Stream.WriteAsync` to return `ValueTask` instead of `Task`. This enables interested overriding stream types to use a reusable/pooled object to avoid `WriteAsync` allocations. - Modifying Stream.CopyToAsync to use the new `Memory`-based overloads of `ReadAsync` and `WriteAsync`. This enables the default `CopyToAsync` implementation to take advantage of any pooling done by derived streams, but even without pooling to take advantage of synchronously completing `ReadAsync`s returning `ValueTask`s that contained an `int` rather than an allocated object. (While I was modifying this, I also removed some unnecessary array clearing that we'd added before later deciding it wasn't needed in general.) - Modifying StreamReader/Writer to use the new `ReadAsync`/`WriteAsync` overloads. --- .../System.Private.CoreLib.Shared.projitems | 1 + .../shared/System/IO/FileStream.Unix.cs | 12 +- .../shared/System/IO/FileStream.Windows.cs | 25 +- src/mscorlib/shared/System/IO/FileStream.cs | 8 +- src/mscorlib/shared/System/IO/MemoryStream.cs | 10 +- src/mscorlib/shared/System/IO/StreamReader.cs | 8 +- src/mscorlib/shared/System/IO/StreamWriter.cs | 4 +- .../shared/System/IO/UnmanagedMemoryStream.cs | 8 +- .../System/IO/UnmanagedMemoryStreamWrapper.cs | 2 +- .../AsyncValueTaskMethodBuilder.cs | 104 ++- .../ConfiguredValueTaskAwaitable.cs | 216 +++-- .../CompilerServices/ValueTaskAwaiter.cs | 187 ++++- .../Threading/Tasks/IValueTaskSource.cs | 77 ++ .../System/Threading/Tasks/ValueTask.cs | 744 ++++++++++++++++-- src/mscorlib/src/System/IO/Stream.cs | 53 +- .../CompilerServices/AsyncMethodBuilder.cs | 54 +- src/mscorlib/src/System/ThrowHelper.cs | 4 +- 17 files changed, 1271 insertions(+), 246 deletions(-) create mode 100644 src/mscorlib/shared/System/Threading/Tasks/IValueTaskSource.cs diff --git a/src/mscorlib/shared/System.Private.CoreLib.Shared.projitems b/src/mscorlib/shared/System.Private.CoreLib.Shared.projitems index b864121125b8..d9ea3a30edf3 100644 --- a/src/mscorlib/shared/System.Private.CoreLib.Shared.projitems +++ b/src/mscorlib/shared/System.Private.CoreLib.Shared.projitems @@ -545,6 +545,7 @@ + diff --git a/src/mscorlib/shared/System/IO/FileStream.Unix.cs b/src/mscorlib/shared/System/IO/FileStream.Unix.cs index 31b9ac53b486..d9fcf6571192 100644 --- a/src/mscorlib/shared/System/IO/FileStream.Unix.cs +++ b/src/mscorlib/shared/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/mscorlib/shared/System/IO/FileStream.Windows.cs b/src/mscorlib/shared/System/IO/FileStream.Windows.cs index 85f045426dbd..291a30bb5385 100644 --- a/src/mscorlib/shared/System/IO/FileStream.Windows.cs +++ b/src/mscorlib/shared/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/mscorlib/shared/System/IO/FileStream.cs b/src/mscorlib/shared/System/IO/FileStream.cs index 4593768bd96f..717b73ff1339 100644 --- a/src/mscorlib/shared/System/IO/FileStream.cs +++ b/src/mscorlib/shared/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/mscorlib/shared/System/IO/MemoryStream.cs b/src/mscorlib/shared/System/IO/MemoryStream.cs index c5e5ea918b44..8e573b749baf 100644 --- a/src/mscorlib/shared/System/IO/MemoryStream.cs +++ b/src/mscorlib/shared/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/mscorlib/shared/System/IO/StreamReader.cs b/src/mscorlib/shared/System/IO/StreamReader.cs index 4e724ddb3004..22ec6e645bb6 100644 --- a/src/mscorlib/shared/System/IO/StreamReader.cs +++ b/src/mscorlib/shared/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/mscorlib/shared/System/IO/StreamWriter.cs b/src/mscorlib/shared/System/IO/StreamWriter.cs index 6cdcd695244c..a37624428066 100644 --- a/src/mscorlib/shared/System/IO/StreamWriter.cs +++ b/src/mscorlib/shared/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/mscorlib/shared/System/IO/UnmanagedMemoryStream.cs b/src/mscorlib/shared/System/IO/UnmanagedMemoryStream.cs index 171113542f11..2f0f34afeea1 100644 --- a/src/mscorlib/shared/System/IO/UnmanagedMemoryStream.cs +++ b/src/mscorlib/shared/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/mscorlib/shared/System/IO/UnmanagedMemoryStreamWrapper.cs b/src/mscorlib/shared/System/IO/UnmanagedMemoryStreamWrapper.cs index 90bb21ac5b28..f34c3c413791 100644 --- a/src/mscorlib/shared/System/IO/UnmanagedMemoryStreamWrapper.cs +++ b/src/mscorlib/shared/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/mscorlib/shared/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs b/src/mscorlib/shared/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs index 49cdaccb0e4b..558b46775cf5 100644 --- a/src/mscorlib/shared/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs +++ b/src/mscorlib/shared/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/mscorlib/shared/System/Runtime/CompilerServices/ConfiguredValueTaskAwaitable.cs b/src/mscorlib/shared/System/Runtime/CompilerServices/ConfiguredValueTaskAwaitable.cs index f22b9d94bf65..295a116a7846 100644 --- a/src/mscorlib/shared/System/Runtime/CompilerServices/ConfiguredValueTaskAwaitable.cs +++ b/src/mscorlib/shared/System/Runtime/CompilerServices/ConfiguredValueTaskAwaitable.cs @@ -5,9 +5,113 @@ using System.Diagnostics; using System.Runtime.InteropServices; using System.Threading.Tasks; +#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, + 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.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.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 +119,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, + 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.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.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/mscorlib/shared/System/Runtime/CompilerServices/ValueTaskAwaiter.cs b/src/mscorlib/shared/System/Runtime/CompilerServices/ValueTaskAwaiter.cs index 3f212d8bf9b8..943b158c6f36 100644 --- a/src/mscorlib/shared/System/Runtime/CompilerServices/ValueTaskAwaiter.cs +++ b/src/mscorlib/shared/System/Runtime/CompilerServices/ValueTaskAwaiter.cs @@ -7,47 +7,194 @@ 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, 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, 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, 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, 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, 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, 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/mscorlib/shared/System/Threading/Tasks/IValueTaskSource.cs b/src/mscorlib/shared/System/Threading/Tasks/IValueTaskSource.cs new file mode 100644 index 000000000000..b44687fe5c44 --- /dev/null +++ b/src/mscorlib/shared/System/Threading/Tasks/IValueTaskSource.cs @@ -0,0 +1,77 @@ +// 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 +{ + /// + /// 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. + ValueTaskSourceStatus Status { get; } + + /// 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. + /// The flags describing the behavior of the continuation. + void OnCompleted(Action continuation, object state, ValueTaskSourceOnCompletedFlags flags); + + /// Gets the result of the . + void GetResult(); + } + + /// 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. + ValueTaskSourceStatus Status { get; } + + /// 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. + /// The flags describing the behavior of the continuation. + void OnCompleted(Action continuation, object state, ValueTaskSourceOnCompletedFlags flags); + + /// Gets the result of the . + TResult GetResult(); + } +} diff --git a/src/mscorlib/shared/System/Threading/Tasks/ValueTask.cs b/src/mscorlib/shared/System/Threading/Tasks/ValueTask.cs index 5edd8501b0c6..cb7c613529ff 100644 --- a/src/mscorlib/shared/System/Threading/Tasks/ValueTask.cs +++ b/src/mscorlib/shared/System/Threading/Tasks/ValueTask.cs @@ -3,71 +3,396 @@ // 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; +#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 a value type that can represent a task object or a synchronously completed success result. + /// + /// 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 + { +#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; + + // 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; + } + + /// Initialize the with a object that represents the operation. + /// The source. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ValueTask(IValueTaskSource source) + { + if (source == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); + } + + _obj = source; + _flags = 0; + } + + /// Non-verified initialization of the struct to the specified values. + /// The object. + /// The flags. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ValueTask(object obj, ValueTaskFlags flags) + { + _obj = obj; + _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; + + /// 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.Status; + if (status != ValueTaskSourceStatus.Pending) + { + try + { + // Propagate any exceptions that may have occurred, then return + // an already successfully completed task. + t.GetResult(); + 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); + 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.Status; + try + { + source.GetResult(); + 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); + } + } + }; + + private IValueTaskSource _source; + + public ValueTaskSourceTask(IValueTaskSource source) + { + _source = source; + source.OnCompleted(s_completionAction, this, ValueTaskSourceOnCompletedFlags.None); + } + } + + /// Gets whether the represents a completed operation. + public bool IsCompleted + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _obj == null || (ObjectIsTask ? UnsafeTask.IsCompleted : UnsafeValueTaskSource.Status != 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.Status == ValueTaskSourceStatus.Succeeded); + } + + /// Gets whether the represents a failed operation. + public bool IsFaulted + { + get => + _obj != null && + (ObjectIsTask ? UnsafeTask.IsFaulted : UnsafeValueTaskSource.Status == 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.Status == 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(); + } + } + } + + /// 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, _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; + + // 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; + _obj = null; _result = result; + _flags = 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 +400,330 @@ public ValueTask(Task task) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.task); } - _task = task; + _obj = task; _result = default; + _flags = ValueTaskFlags.ObjectIsTask; + } + + /// Initialize the with a object that represents the operation. + /// The source. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ValueTask(IValueTaskSource source) + { + if (source == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); + } + + _obj = source; + _result = default; + _flags = 0; + } + + /// Non-verified initialization of the struct to the specified values. + /// The object. + /// The result. + /// The flags. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ValueTask(object obj, TResult result, ValueTaskFlags flags) + { + _obj = obj; + _result = result; + _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 : 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); + Task.FromResult(_result) : #else - AsyncTaskMethodBuilder.GetTaskForResult(_result); + AsyncTaskMethodBuilder.GetTaskForResult(_result) : #endif + ObjectIsTask ? UnsafeTask : + GetTaskForValueTaskSource(); - 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(); + /// Gets a that may be used at any point in the future. + public ValueTask Preserve() => _obj == null ? this : new ValueTask(AsTask()); - [MethodImpl(MethodImplOptions.NoInlining)] - private Task GetTaskForResultNoInlining() => + /// Creates a to represent the . + private Task GetTaskForValueTaskSource() + { + IValueTaskSource t = UnsafeValueTaskSource; + ValueTaskSourceStatus status = t.Status; + 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(_result); + Task.FromResult(t.GetResult()); #else - AsyncTaskMethodBuilder.GetTaskForResult(_result); + AsyncTaskMethodBuilder.GetTaskForResult(t.GetResult()); #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); + 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.Status; + try + { + vtst.TrySetResult(source.GetResult()); + } + 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); + } + } + }; + + private IValueTaskSource _source; + + public ValueTaskSourceTask(IValueTaskSource source) + { + _source = source; + source.OnCompleted(s_completionAction, this, 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.Status != 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.Status == 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.Status == 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.Status == 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; + } + + if (ObjectIsTask) + { +#if netstandard + return UnsafeTask.GetAwaiter().GetResult(); +#else + Task t = UnsafeTask; + TaskAwaiter.ValidateEnd(t); + return t.ResultOnSuccess; +#endif + } + + return UnsafeValueTaskSource.GetResult(); + } + } - /// Gets an awaiter for this value. + /// 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, _flags | Unsafe.As(ref avoidCapture))); + } /// Gets a string-representation of this . public override string ToString() @@ -180,4 +740,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 + } } diff --git a/src/mscorlib/src/System/IO/Stream.cs b/src/mscorlib/src/System/IO/Stream.cs index 63cc0c9035f4..1304a2e42d6e 100644 --- a/src/mscorlib/src/System/IO/Stream.cs +++ b/src/mscorlib/src/System/IO/Stream.cs @@ -137,27 +137,19 @@ public virtual Task CopyToAsync(Stream destination, Int32 bufferSize, Cancellati private async Task CopyToAsyncInternal(Stream destination, Int32 bufferSize, CancellationToken cancellationToken) { - Debug.Assert(destination != null); - Debug.Assert(bufferSize > 0); - Debug.Assert(CanRead); - Debug.Assert(destination.CanWrite); - byte[] buffer = ArrayPool.Shared.Rent(bufferSize); - bufferSize = 0; // reuse same field for high water mark to avoid needing another field in the state machine try { while (true) { - int bytesRead = await ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false); + int bytesRead = await ReadAsync(new Memory(buffer), cancellationToken).ConfigureAwait(false); if (bytesRead == 0) break; - if (bytesRead > bufferSize) bufferSize = bytesRead; - await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false); + await destination.WriteAsync(new ReadOnlyMemory(buffer, 0, bytesRead), cancellationToken).ConfigureAwait(false); } } finally { - Array.Clear(buffer, 0, bufferSize); // clear only the most we used - ArrayPool.Shared.Return(buffer, clearArray: false); + ArrayPool.Shared.Return(buffer); } } @@ -176,20 +168,17 @@ public virtual void CopyTo(Stream destination, int bufferSize) StreamHelpers.ValidateCopyToArgs(this, destination, bufferSize); byte[] buffer = ArrayPool.Shared.Rent(bufferSize); - int highwaterMark = 0; try { int read; while ((read = Read(buffer, 0, buffer.Length)) != 0) { - if (read > highwaterMark) highwaterMark = read; destination.Write(buffer, 0, read); } } finally { - Array.Clear(buffer, 0, highwaterMark); // clear only the most we used - ArrayPool.Shared.Return(buffer, clearArray: false); + ArrayPool.Shared.Return(buffer); } } @@ -694,29 +683,29 @@ public virtual Task WriteAsync(Byte[] buffer, int offset, int count, Cancellatio : BeginEndWriteAsync(buffer, offset, count); } - public virtual Task WriteAsync(ReadOnlyMemory source, CancellationToken cancellationToken = default(CancellationToken)) + public virtual ValueTask WriteAsync(ReadOnlyMemory source, CancellationToken cancellationToken = default(CancellationToken)) { if (MemoryMarshal.TryGetArray(source, out ArraySegment array)) { - return WriteAsync(array.Array, array.Offset, array.Count, cancellationToken); + return new ValueTask(WriteAsync(array.Array, array.Offset, array.Count, cancellationToken)); } else { byte[] buffer = ArrayPool.Shared.Rent(source.Length); source.Span.CopyTo(buffer); - return FinishWriteAsync(WriteAsync(buffer, 0, source.Length, cancellationToken), buffer); + return new ValueTask(FinishWriteAsync(WriteAsync(buffer, 0, source.Length, cancellationToken), buffer)); + } + } - async Task FinishWriteAsync(Task writeTask, byte[] localBuffer) - { - try - { - await writeTask.ConfigureAwait(false); - } - finally - { - ArrayPool.Shared.Return(localBuffer); - } - } + private async Task FinishWriteAsync(Task writeTask, byte[] localBuffer) + { + try + { + await writeTask.ConfigureAwait(false); + } + finally + { + ArrayPool.Shared.Return(localBuffer); } } @@ -1018,11 +1007,11 @@ public override Task WriteAsync(Byte[] buffer, int offset, int count, Cancellati Task.CompletedTask; } - public override Task WriteAsync(ReadOnlyMemory source, CancellationToken cancellationToken = default(CancellationToken)) + public override ValueTask WriteAsync(ReadOnlyMemory source, CancellationToken cancellationToken = default(CancellationToken)) { return cancellationToken.IsCancellationRequested ? - Task.FromCanceled(cancellationToken) : - Task.CompletedTask; + new ValueTask(Task.FromCanceled(cancellationToken)) : + default; } public override void WriteByte(byte value) diff --git a/src/mscorlib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs b/src/mscorlib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs index abe324e0774b..4603ee75c1b2 100644 --- a/src/mscorlib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs +++ b/src/mscorlib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs @@ -377,7 +377,8 @@ public void AwaitUnsafeOnCompleted( IAsyncStateMachineBox box = GetStateMachineBox(ref stateMachine); // The null tests here ensure that the jit can optimize away the interface - // tests when TAwaiter is is a ref type. + // tests when TAwaiter is a ref type. + if ((null != (object)default(TAwaiter)) && (awaiter is ITaskAwaiter)) { ref TaskAwaiter ta = ref Unsafe.As(ref awaiter); // relies on TaskAwaiter/TaskAwaiter having the same layout @@ -390,17 +391,25 @@ public void AwaitUnsafeOnCompleted( } else if ((null != (object)default(TAwaiter)) && (awaiter is IValueTaskAwaiter)) { - Task t = ((IValueTaskAwaiter)awaiter).GetTask(); - TaskAwaiter.UnsafeOnCompletedInternal(t, box, continueOnCapturedContext: true); - } - else if ((null != (object)default(TAwaiter)) && (awaiter is IConfiguredValueTaskAwaiter)) - { - Task t = ((IConfiguredValueTaskAwaiter)awaiter).GetTask(out bool continueOnCapturedContext); - TaskAwaiter.UnsafeOnCompletedInternal(t, box, continueOnCapturedContext); + try + { + ((IValueTaskAwaiter)awaiter).AwaitUnsafeOnCompleted(box); + } + catch (Exception e) + { + // Whereas with Task the code that hooks up and invokes the continuation is all local to corelib, + // with ValueTaskAwaiter we may be calling out to an arbitrary implementation of IValueTaskSource + // wrapped in the ValueTask, and as such we protect against errant exceptions that may emerge. + // We don't want such exceptions propagating back into the async method, which can't handle + // exceptions well at that location in the state machine, especially if the exception may occur + // after the ValueTaskAwaiter already successfully hooked up the callback, in which case it's possible + // two different flows of execution could end up happening in the same async method call. + AsyncMethodBuilderCore.ThrowAsync(e, targetContext: null); + } } - // The awaiter isn't specially known. Fall back to doing a normal await. else { + // The awaiter isn't specially known. Fall back to doing a normal await. try { awaiter.UnsafeOnCompleted(box.MoveNextAction); @@ -523,13 +532,17 @@ private class AsyncStateMachineBox : public ExecutionContext Context; /// A delegate to the method. - public Action MoveNextAction => - _moveNextAction ?? - (_moveNextAction = AsyncCausalityTracer.LoggingOn ? AsyncMethodBuilderCore.OutputAsyncCausalityEvents(this, new Action(MoveNext)) : new Action(MoveNext)); + public Action MoveNextAction => _moveNextAction ?? (_moveNextAction = new Action(MoveNext)); /// Calls MoveNext on public void MoveNext() { + bool loggingOn = AsyncCausalityTracer.LoggingOn; + if (loggingOn) + { + AsyncCausalityTracer.TraceSynchronousWorkStart(CausalityTraceLevel.Required, Id, CausalitySynchronousWork.Execution); + } + ExecutionContext context = Context; if (context == null) { @@ -547,6 +560,11 @@ public void MoveNext() { GC.SuppressFinalize(this); } + + if (loggingOn) + { + AsyncCausalityTracer.TraceSynchronousWorkCompletion(CausalityTraceLevel.Required, CausalitySynchronousWork.Execution); + } } /// @@ -554,8 +572,8 @@ public void MoveNext() /// that the state machine object may be queued directly as a continuation into a Task's /// continuation slot/list. /// - /// The completing task that caused this method to be invoked, if there was one. - void ITaskCompletionAction.Invoke(Task completedTask) => MoveNext(); + /// The completing task that caused this method to be invoked, if there was one. + void ITaskCompletionAction.Invoke(Task ignored) => MoveNext(); /// Signals to Task's continuation logic that runs arbitrary user code via MoveNext. bool ITaskCompletionAction.InvokeMayRunArbitraryCode => true; @@ -973,14 +991,6 @@ internal static string GetAsyncStateMachineDescription(IAsyncStateMachine stateM return sb.ToString(); } - internal static Action OutputAsyncCausalityEvents(Task task, Action continuation) => - CreateContinuationWrapper(continuation, (innerContinuation, innerTask) => - { - AsyncCausalityTracer.TraceSynchronousWorkStart(CausalityTraceLevel.Required, innerTask.Id, CausalitySynchronousWork.Execution); - innerContinuation.Invoke(); // Invoke the original continuation - AsyncCausalityTracer.TraceSynchronousWorkCompletion(CausalityTraceLevel.Required, CausalitySynchronousWork.Execution); - }, task); - internal static Action CreateContinuationWrapper(Action continuation, Action invokeAction, Task innerTask) => new ContinuationWrapper(continuation, invokeAction, innerTask).Invoke; diff --git a/src/mscorlib/src/System/ThrowHelper.cs b/src/mscorlib/src/System/ThrowHelper.cs index 06c1d45b016d..6b755a13fcac 100644 --- a/src/mscorlib/src/System/ThrowHelper.cs +++ b/src/mscorlib/src/System/ThrowHelper.cs @@ -477,7 +477,9 @@ internal enum ExceptionArgument start, format, culture, - comparable + comparable, + source, + state } // From d4e78e8ebaf5c9accdea44385257efdffdbb77d3 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Wed, 28 Feb 2018 22:25:02 -0500 Subject: [PATCH 2/3] Add token to IValueTaskSource --- .../ConfiguredValueTaskAwaitable.cs | 12 +- .../CompilerServices/ValueTaskAwaiter.cs | 12 +- .../Threading/Tasks/IValueTaskSource.cs | 20 ++-- .../System/Threading/Tasks/ValueTask.cs | 104 +++++++++++------- 4 files changed, 91 insertions(+), 57 deletions(-) diff --git a/src/mscorlib/shared/System/Runtime/CompilerServices/ConfiguredValueTaskAwaitable.cs b/src/mscorlib/shared/System/Runtime/CompilerServices/ConfiguredValueTaskAwaitable.cs index 295a116a7846..0d7e3d11192c 100644 --- a/src/mscorlib/shared/System/Runtime/CompilerServices/ConfiguredValueTaskAwaitable.cs +++ b/src/mscorlib/shared/System/Runtime/CompilerServices/ConfiguredValueTaskAwaitable.cs @@ -63,7 +63,7 @@ public void OnCompleted(Action continuation) } else if (_value._obj != null) { - _value.UnsafeValueTaskSource.OnCompleted(ValueTaskAwaiter.s_invokeActionDelegate, continuation, + _value.UnsafeValueTaskSource.OnCompleted(ValueTaskAwaiter.s_invokeActionDelegate, continuation, _value._token, ValueTaskSourceOnCompletedFlags.FlowExecutionContext | (_value.ContinueOnCapturedContext ? ValueTaskSourceOnCompletedFlags.UseSchedulingContext : ValueTaskSourceOnCompletedFlags.None)); } @@ -82,7 +82,7 @@ public void UnsafeOnCompleted(Action continuation) } else if (_value._obj != null) { - _value.UnsafeValueTaskSource.OnCompleted(ValueTaskAwaiter.s_invokeActionDelegate, continuation, + _value.UnsafeValueTaskSource.OnCompleted(ValueTaskAwaiter.s_invokeActionDelegate, continuation, _value._token, _value.ContinueOnCapturedContext ? ValueTaskSourceOnCompletedFlags.UseSchedulingContext : ValueTaskSourceOnCompletedFlags.None); } else @@ -100,7 +100,7 @@ void IValueTaskAwaiter.AwaitUnsafeOnCompleted(IAsyncStateMachineBox box) } else if (_value._obj != null) { - _value.UnsafeValueTaskSource.OnCompleted(ValueTaskAwaiter.s_invokeAsyncStateMachineBox, box, + _value.UnsafeValueTaskSource.OnCompleted(ValueTaskAwaiter.s_invokeAsyncStateMachineBox, box, _value._token, _value.ContinueOnCapturedContext ? ValueTaskSourceOnCompletedFlags.UseSchedulingContext : ValueTaskSourceOnCompletedFlags.None); } else @@ -165,7 +165,7 @@ public void OnCompleted(Action continuation) } else if (_value._obj != null) { - _value.UnsafeValueTaskSource.OnCompleted(ValueTaskAwaiter.s_invokeActionDelegate, continuation, + _value.UnsafeValueTaskSource.OnCompleted(ValueTaskAwaiter.s_invokeActionDelegate, continuation, _value._token, ValueTaskSourceOnCompletedFlags.FlowExecutionContext | (_value.ContinueOnCapturedContext ? ValueTaskSourceOnCompletedFlags.UseSchedulingContext : ValueTaskSourceOnCompletedFlags.None)); } @@ -184,7 +184,7 @@ public void UnsafeOnCompleted(Action continuation) } else if (_value._obj != null) { - _value.UnsafeValueTaskSource.OnCompleted(ValueTaskAwaiter.s_invokeActionDelegate, continuation, + _value.UnsafeValueTaskSource.OnCompleted(ValueTaskAwaiter.s_invokeActionDelegate, continuation, _value._token, _value.ContinueOnCapturedContext ? ValueTaskSourceOnCompletedFlags.UseSchedulingContext : ValueTaskSourceOnCompletedFlags.None); } else @@ -202,7 +202,7 @@ void IValueTaskAwaiter.AwaitUnsafeOnCompleted(IAsyncStateMachineBox box) } else if (_value._obj != null) { - _value.UnsafeValueTaskSource.OnCompleted(ValueTaskAwaiter.s_invokeAsyncStateMachineBox, box, + _value.UnsafeValueTaskSource.OnCompleted(ValueTaskAwaiter.s_invokeAsyncStateMachineBox, box, _value._token, _value.ContinueOnCapturedContext ? ValueTaskSourceOnCompletedFlags.UseSchedulingContext : ValueTaskSourceOnCompletedFlags.None); } else diff --git a/src/mscorlib/shared/System/Runtime/CompilerServices/ValueTaskAwaiter.cs b/src/mscorlib/shared/System/Runtime/CompilerServices/ValueTaskAwaiter.cs index 943b158c6f36..4b3df947adaa 100644 --- a/src/mscorlib/shared/System/Runtime/CompilerServices/ValueTaskAwaiter.cs +++ b/src/mscorlib/shared/System/Runtime/CompilerServices/ValueTaskAwaiter.cs @@ -53,7 +53,7 @@ public void OnCompleted(Action continuation) } else if (_value._obj != null) { - _value.UnsafeValueTaskSource.OnCompleted(s_invokeActionDelegate, continuation, ValueTaskSourceOnCompletedFlags.UseSchedulingContext | ValueTaskSourceOnCompletedFlags.FlowExecutionContext); + _value.UnsafeValueTaskSource.OnCompleted(s_invokeActionDelegate, continuation, _value._token, ValueTaskSourceOnCompletedFlags.UseSchedulingContext | ValueTaskSourceOnCompletedFlags.FlowExecutionContext); } else { @@ -70,7 +70,7 @@ public void UnsafeOnCompleted(Action continuation) } else if (_value._obj != null) { - _value.UnsafeValueTaskSource.OnCompleted(s_invokeActionDelegate, continuation, ValueTaskSourceOnCompletedFlags.UseSchedulingContext); + _value.UnsafeValueTaskSource.OnCompleted(s_invokeActionDelegate, continuation, _value._token, ValueTaskSourceOnCompletedFlags.UseSchedulingContext); } else { @@ -87,7 +87,7 @@ void IValueTaskAwaiter.AwaitUnsafeOnCompleted(IAsyncStateMachineBox box) } else if (_value._obj != null) { - _value.UnsafeValueTaskSource.OnCompleted(s_invokeAsyncStateMachineBox, box, ValueTaskSourceOnCompletedFlags.UseSchedulingContext); + _value.UnsafeValueTaskSource.OnCompleted(s_invokeAsyncStateMachineBox, box, _value._token, ValueTaskSourceOnCompletedFlags.UseSchedulingContext); } else { @@ -144,7 +144,7 @@ public void OnCompleted(Action continuation) } else if (_value._obj != null) { - _value.UnsafeValueTaskSource.OnCompleted(ValueTaskAwaiter.s_invokeActionDelegate, continuation, ValueTaskSourceOnCompletedFlags.UseSchedulingContext | ValueTaskSourceOnCompletedFlags.FlowExecutionContext); + _value.UnsafeValueTaskSource.OnCompleted(ValueTaskAwaiter.s_invokeActionDelegate, continuation, _value._token, ValueTaskSourceOnCompletedFlags.UseSchedulingContext | ValueTaskSourceOnCompletedFlags.FlowExecutionContext); } else { @@ -161,7 +161,7 @@ public void UnsafeOnCompleted(Action continuation) } else if (_value._obj != null) { - _value.UnsafeValueTaskSource.OnCompleted(ValueTaskAwaiter.s_invokeActionDelegate, continuation, ValueTaskSourceOnCompletedFlags.UseSchedulingContext); + _value.UnsafeValueTaskSource.OnCompleted(ValueTaskAwaiter.s_invokeActionDelegate, continuation, _value._token, ValueTaskSourceOnCompletedFlags.UseSchedulingContext); } else { @@ -178,7 +178,7 @@ void IValueTaskAwaiter.AwaitUnsafeOnCompleted(IAsyncStateMachineBox box) } else if (_value._obj != null) { - _value.UnsafeValueTaskSource.OnCompleted(ValueTaskAwaiter.s_invokeAsyncStateMachineBox, box, ValueTaskSourceOnCompletedFlags.UseSchedulingContext); + _value.UnsafeValueTaskSource.OnCompleted(ValueTaskAwaiter.s_invokeAsyncStateMachineBox, box, _value._token, ValueTaskSourceOnCompletedFlags.UseSchedulingContext); } else { diff --git a/src/mscorlib/shared/System/Threading/Tasks/IValueTaskSource.cs b/src/mscorlib/shared/System/Threading/Tasks/IValueTaskSource.cs index b44687fe5c44..7c7312ac06a5 100644 --- a/src/mscorlib/shared/System/Threading/Tasks/IValueTaskSource.cs +++ b/src/mscorlib/shared/System/Threading/Tasks/IValueTaskSource.cs @@ -46,16 +46,19 @@ public enum ValueTaskSourceStatus public interface IValueTaskSource { /// Gets the status of the current operation. - ValueTaskSourceStatus Status { get; } - + /// 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, ValueTaskSourceOnCompletedFlags flags); + void OnCompleted(Action continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags); /// Gets the result of the . - void GetResult(); + /// Opaque value that was provided to the 's constructor. + void GetResult(short token); } /// Represents an object that can be wrapped by a . @@ -63,15 +66,18 @@ public interface IValueTaskSource public interface IValueTaskSource { /// Gets the status of the current operation. - ValueTaskSourceStatus Status { get; } + /// 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, ValueTaskSourceOnCompletedFlags flags); + void OnCompleted(Action continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags); /// Gets the result of the . - TResult GetResult(); + /// Opaque value that was provided to the 's constructor. + TResult GetResult(short token); } } diff --git a/src/mscorlib/shared/System/Threading/Tasks/ValueTask.cs b/src/mscorlib/shared/System/Threading/Tasks/ValueTask.cs index cb7c613529ff..b6689c1d82fe 100644 --- a/src/mscorlib/shared/System/Threading/Tasks/ValueTask.cs +++ b/src/mscorlib/shared/System/Threading/Tasks/ValueTask.cs @@ -12,12 +12,12 @@ namespace System.Threading.Tasks { - /// Provides a value type that can represent a task object or a synchronously completed success 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 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. + /// 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)] @@ -32,6 +32,8 @@ namespace System.Threading.Tasks 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. @@ -46,13 +48,16 @@ public ValueTask(Task 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) + public ValueTask(IValueTaskSource source, short token) { if (source == null) { @@ -60,16 +65,20 @@ public ValueTask(IValueTaskSource 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, ValueTaskFlags flags) + private ValueTask(object obj, short token, ValueTaskFlags flags) { _obj = obj; + _token = token; _flags = flags; } @@ -120,7 +129,7 @@ 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; + 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) => @@ -154,14 +163,14 @@ public Task AsTask() => private Task GetTaskForValueTaskSource() { IValueTaskSource t = UnsafeValueTaskSource; - ValueTaskSourceStatus status = t.Status; + 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(); + t.GetResult(_token); return #if netstandard s_completedTask; @@ -208,7 +217,7 @@ private Task GetTaskForValueTaskSource() } } - var m = new ValueTaskSourceTask(t); + var m = new ValueTaskSourceTask(t, _token); return #if netstandard m.Task; @@ -238,10 +247,10 @@ private sealed class ValueTaskSourceTask : } vtst._source = null; - ValueTaskSourceStatus status = source.Status; + ValueTaskSourceStatus status = source.GetStatus(vtst._token); try { - source.GetResult(); + source.GetResult(vtst._token); vtst.TrySetResult(default); } catch (Exception exc) @@ -268,12 +277,16 @@ private sealed class ValueTaskSourceTask : } }; + /// The associated . private IValueTaskSource _source; + /// The token to pass through to operations on + private readonly short _token; - public ValueTaskSourceTask(IValueTaskSource source) + public ValueTaskSourceTask(IValueTaskSource source, short token) { + _token = token; _source = source; - source.OnCompleted(s_completionAction, this, ValueTaskSourceOnCompletedFlags.None); + source.OnCompleted(s_completionAction, this, token, ValueTaskSourceOnCompletedFlags.None); } } @@ -281,7 +294,7 @@ public ValueTaskSourceTask(IValueTaskSource source) public bool IsCompleted { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _obj == null || (ObjectIsTask ? UnsafeTask.IsCompleted : UnsafeValueTaskSource.Status != ValueTaskSourceStatus.Pending); + get => _obj == null || (ObjectIsTask ? UnsafeTask.IsCompleted : UnsafeValueTaskSource.GetStatus(_token) != ValueTaskSourceStatus.Pending); } /// Gets whether the represents a successfully completed operation. @@ -296,7 +309,7 @@ public bool IsCompletedSuccessfully #else UnsafeTask.IsCompletedSuccessfully : #endif - UnsafeValueTaskSource.Status == ValueTaskSourceStatus.Succeeded); + UnsafeValueTaskSource.GetStatus(_token) == ValueTaskSourceStatus.Succeeded); } /// Gets whether the represents a failed operation. @@ -304,7 +317,7 @@ public bool IsFaulted { get => _obj != null && - (ObjectIsTask ? UnsafeTask.IsFaulted : UnsafeValueTaskSource.Status == ValueTaskSourceStatus.Faulted); + (ObjectIsTask ? UnsafeTask.IsFaulted : UnsafeValueTaskSource.GetStatus(_token) == ValueTaskSourceStatus.Faulted); } /// Gets whether the represents a canceled operation. @@ -317,7 +330,7 @@ public bool IsCanceled { get => _obj != null && - (ObjectIsTask ? UnsafeTask.IsCanceled : UnsafeValueTaskSource.Status == ValueTaskSourceStatus.Canceled); + (ObjectIsTask ? UnsafeTask.IsCanceled : UnsafeValueTaskSource.GetStatus(_token) == ValueTaskSourceStatus.Canceled); } /// Throws the exception that caused the to fail. If it completed successfully, nothing is thrown. @@ -337,7 +350,7 @@ internal void ThrowIfCompletedUnsuccessfully() } else { - UnsafeValueTaskSource.GetResult(); + UnsafeValueTaskSource.GetResult(_token); } } } @@ -354,7 +367,7 @@ public ConfiguredValueTaskAwaitable ConfigureAwait(bool continueOnCapturedContex { // TODO: Simplify once https://github.com/dotnet/coreclr/pull/16138 is fixed. bool avoidCapture = !continueOnCapturedContext; - return new ConfiguredValueTaskAwaitable(new ValueTask(_obj, _flags | Unsafe.As(ref avoidCapture))); + return new ConfiguredValueTaskAwaitable(new ValueTask(_obj, _token, _flags | Unsafe.As(ref avoidCapture))); } } @@ -376,6 +389,8 @@ public ConfiguredValueTaskAwaitable ConfigureAwait(bool continueOnCapturedContex 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). @@ -385,9 +400,11 @@ public ConfiguredValueTaskAwaitable ConfigureAwait(bool continueOnCapturedContex [MethodImpl(MethodImplOptions.AggressiveInlining)] public ValueTask(TResult result) { - _obj = null; _result = result; + + _obj = null; _flags = 0; + _token = 0; } /// Initialize the with a that represents the operation. @@ -401,14 +418,17 @@ public ValueTask(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) + public ValueTask(IValueTaskSource source, short token) { if (source == null) { @@ -416,6 +436,8 @@ public ValueTask(IValueTaskSource source) } _obj = source; + _token = token; + _result = default; _flags = 0; } @@ -423,12 +445,14 @@ public ValueTask(IValueTaskSource source) /// 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, ValueTaskFlags flags) + private ValueTask(object obj, TResult result, short token, ValueTaskFlags flags) { _obj = obj; _result = result; + _token = token; _flags = flags; } @@ -484,7 +508,7 @@ obj is ValueTask && /// Returns a value indicating whether this value is equal to a specified value. public bool Equals(ValueTask other) => _obj != null || other._obj != null ? - _obj == other._obj : + _obj == other._obj && _token == other._token : EqualityComparer.Default.Equals(_result, other._result); /// Returns a value indicating whether two values are equal. @@ -519,7 +543,7 @@ public Task AsTask() => private Task GetTaskForValueTaskSource() { IValueTaskSource t = UnsafeValueTaskSource; - ValueTaskSourceStatus status = t.Status; + ValueTaskSourceStatus status = t.GetStatus(_token); if (status != ValueTaskSourceStatus.Pending) { try @@ -528,9 +552,9 @@ private Task GetTaskForValueTaskSource() // If any exception occurred, propagate it return #if netstandard - Task.FromResult(t.GetResult()); + Task.FromResult(t.GetResult(_token)); #else - AsyncTaskMethodBuilder.GetTaskForResult(t.GetResult()); + AsyncTaskMethodBuilder.GetTaskForResult(t.GetResult(_token)); #endif // If status is Faulted or Canceled, GetResult should throw. But @@ -572,7 +596,7 @@ private Task GetTaskForValueTaskSource() } } - var m = new ValueTaskSourceTask(t); + var m = new ValueTaskSourceTask(t, _token); return #if netstandard m.Task; @@ -602,10 +626,10 @@ private sealed class ValueTaskSourceTask : } vtst._source = null; - ValueTaskSourceStatus status = source.Status; + ValueTaskSourceStatus status = source.GetStatus(vtst._token); try { - vtst.TrySetResult(source.GetResult()); + vtst.TrySetResult(source.GetResult(vtst._token)); } catch (Exception exc) { @@ -631,12 +655,16 @@ private sealed class ValueTaskSourceTask : } }; + /// The associated . private IValueTaskSource _source; + /// The token to pass through to operations on + private readonly short _token; - public ValueTaskSourceTask(IValueTaskSource source) + public ValueTaskSourceTask(IValueTaskSource source, short token) { _source = source; - source.OnCompleted(s_completionAction, this, ValueTaskSourceOnCompletedFlags.None); + _token = token; + source.OnCompleted(s_completionAction, this, token, ValueTaskSourceOnCompletedFlags.None); } } @@ -644,7 +672,7 @@ public ValueTaskSourceTask(IValueTaskSource source) public bool IsCompleted { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _obj == null || (ObjectIsTask ? UnsafeTask.IsCompleted : UnsafeValueTaskSource.Status != ValueTaskSourceStatus.Pending); + get => _obj == null || (ObjectIsTask ? UnsafeTask.IsCompleted : UnsafeValueTaskSource.GetStatus(_token) != ValueTaskSourceStatus.Pending); } /// Gets whether the represents a successfully completed operation. @@ -659,7 +687,7 @@ public bool IsCompletedSuccessfully #else UnsafeTask.IsCompletedSuccessfully : #endif - UnsafeValueTaskSource.Status == ValueTaskSourceStatus.Succeeded); + UnsafeValueTaskSource.GetStatus(_token) == ValueTaskSourceStatus.Succeeded); } /// Gets whether the represents a failed operation. @@ -667,7 +695,7 @@ public bool IsFaulted { get => _obj != null && - (ObjectIsTask ? UnsafeTask.IsFaulted : UnsafeValueTaskSource.Status == ValueTaskSourceStatus.Faulted); + (ObjectIsTask ? UnsafeTask.IsFaulted : UnsafeValueTaskSource.GetStatus(_token) == ValueTaskSourceStatus.Faulted); } /// Gets whether the represents a canceled operation. @@ -680,7 +708,7 @@ public bool IsCanceled { get => _obj != null && - (ObjectIsTask ? UnsafeTask.IsCanceled : UnsafeValueTaskSource.Status == ValueTaskSourceStatus.Canceled); + (ObjectIsTask ? UnsafeTask.IsCanceled : UnsafeValueTaskSource.GetStatus(_token) == ValueTaskSourceStatus.Canceled); } /// Gets the result. @@ -705,7 +733,7 @@ public TResult Result #endif } - return UnsafeValueTaskSource.GetResult(); + return UnsafeValueTaskSource.GetResult(_token); } } @@ -722,7 +750,7 @@ public ConfiguredValueTaskAwaitable ConfigureAwait(bool continueOnCaptu { // TODO: Simplify once https://github.com/dotnet/coreclr/pull/16138 is fixed. bool avoidCapture = !continueOnCapturedContext; - return new ConfiguredValueTaskAwaitable(new ValueTask(_obj, _result, _flags | Unsafe.As(ref avoidCapture))); + return new ConfiguredValueTaskAwaitable(new ValueTask(_obj, _result, _token, _flags | Unsafe.As(ref avoidCapture))); } /// Gets a string-representation of this . From f9a312befe71a6c68c908ed09234a3ebc2d54b6c Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Wed, 28 Feb 2018 22:43:29 -0500 Subject: [PATCH 3/3] Move IValueTaskSource and friends to System.Threading.Tasks.Sources Group these more advanced types into a subnamespace. --- src/mscorlib/shared/System.Private.CoreLib.Shared.projitems | 2 +- .../Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs | 4 ++-- .../Runtime/CompilerServices/ConfiguredValueTaskAwaitable.cs | 2 ++ .../System/Runtime/CompilerServices/ValueTaskAwaiter.cs | 1 + .../System/Threading/Tasks/{ => Sources}/IValueTaskSource.cs | 2 +- src/mscorlib/shared/System/Threading/Tasks/ValueTask.cs | 2 ++ 6 files changed, 9 insertions(+), 4 deletions(-) rename src/mscorlib/shared/System/Threading/Tasks/{ => Sources}/IValueTaskSource.cs (99%) diff --git a/src/mscorlib/shared/System.Private.CoreLib.Shared.projitems b/src/mscorlib/shared/System.Private.CoreLib.Shared.projitems index d9ea3a30edf3..fafe4b929222 100644 --- a/src/mscorlib/shared/System.Private.CoreLib.Shared.projitems +++ b/src/mscorlib/shared/System.Private.CoreLib.Shared.projitems @@ -545,13 +545,13 @@ - + diff --git a/src/mscorlib/shared/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs b/src/mscorlib/shared/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs index 558b46775cf5..0e1220d11900 100644 --- a/src/mscorlib/shared/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs +++ b/src/mscorlib/shared/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs @@ -85,7 +85,7 @@ public ValueTask 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 awaiter. /// The state machine. public void AwaitOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : INotifyCompletion @@ -98,7 +98,7 @@ public void AwaitOnCompleted(ref TAwaiter awaiter, ref /// 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 awaiter. /// The state machine. [SecuritySafeCritical] public void AwaitUnsafeOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) diff --git a/src/mscorlib/shared/System/Runtime/CompilerServices/ConfiguredValueTaskAwaitable.cs b/src/mscorlib/shared/System/Runtime/CompilerServices/ConfiguredValueTaskAwaitable.cs index 0d7e3d11192c..65d3d5670d86 100644 --- a/src/mscorlib/shared/System/Runtime/CompilerServices/ConfiguredValueTaskAwaitable.cs +++ b/src/mscorlib/shared/System/Runtime/CompilerServices/ConfiguredValueTaskAwaitable.cs @@ -5,6 +5,8 @@ using System.Diagnostics; using System.Runtime.InteropServices; using System.Threading.Tasks; +using System.Threading.Tasks.Sources; + #if !netstandard using Internal.Runtime.CompilerServices; #endif diff --git a/src/mscorlib/shared/System/Runtime/CompilerServices/ValueTaskAwaiter.cs b/src/mscorlib/shared/System/Runtime/CompilerServices/ValueTaskAwaiter.cs index 4b3df947adaa..0414a05a0dfb 100644 --- a/src/mscorlib/shared/System/Runtime/CompilerServices/ValueTaskAwaiter.cs +++ b/src/mscorlib/shared/System/Runtime/CompilerServices/ValueTaskAwaiter.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Threading.Tasks; +using System.Threading.Tasks.Sources; namespace System.Runtime.CompilerServices { diff --git a/src/mscorlib/shared/System/Threading/Tasks/IValueTaskSource.cs b/src/mscorlib/shared/System/Threading/Tasks/Sources/IValueTaskSource.cs similarity index 99% rename from src/mscorlib/shared/System/Threading/Tasks/IValueTaskSource.cs rename to src/mscorlib/shared/System/Threading/Tasks/Sources/IValueTaskSource.cs index 7c7312ac06a5..3c1e8830a10d 100644 --- a/src/mscorlib/shared/System/Threading/Tasks/IValueTaskSource.cs +++ b/src/mscorlib/shared/System/Threading/Tasks/Sources/IValueTaskSource.cs @@ -2,7 +2,7 @@ // 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 +namespace System.Threading.Tasks.Sources { /// /// Flags passed from and to diff --git a/src/mscorlib/shared/System/Threading/Tasks/ValueTask.cs b/src/mscorlib/shared/System/Threading/Tasks/ValueTask.cs index b6689c1d82fe..6c45ed65681e 100644 --- a/src/mscorlib/shared/System/Threading/Tasks/ValueTask.cs +++ b/src/mscorlib/shared/System/Threading/Tasks/ValueTask.cs @@ -6,6 +6,8 @@ using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Threading.Tasks.Sources; + #if !netstandard using Internal.Runtime.CompilerServices; #endif