diff --git a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs index 340d7a155c9fe..f5a37cff0076d 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs @@ -115,30 +115,26 @@ public SourceText GetText(CancellationToken cancellationToken) return text; } - public static ValueTask FromTextDocumentStateAsync( + public static async ValueTask FromTextDocumentStateAsync( TextDocumentState state, CancellationToken cancellationToken) { if (state.TextAndVersionSource.TextLoader is SerializableSourceTextLoader serializableLoader) { // If we're already pointing at a serializable loader, we can just use that directly. - return new(serializableLoader.SerializableSourceText); + return serializableLoader.SerializableSourceText; } else if (state.StorageHandle is TemporaryStorageTextHandle storageHandle) { // Otherwise, if we're pointing at a memory mapped storage location, we can create the source text that directly wraps that. - return new(new SerializableSourceText(storageHandle)); + return new SerializableSourceText(storageHandle); } else { // Otherwise, the state object has reified the text into some other form, and dumped any original // information on how it got it. In that case, we create a new text instance to represent the serializable // source text out of. - - return SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync( - static (state, cancellationToken) => state.GetTextAsync(cancellationToken), - static (text, _) => new SerializableSourceText(text, text.GetContentHash()), - state, - cancellationToken); + var text = await state.GetTextAsync(cancellationToken).ConfigureAwait(false); + return new SerializableSourceText(text, text.GetContentHash()); } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState_Checksum.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState_Checksum.cs index f9a3bd91b7fe4..06023e4d72aad 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState_Checksum.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState_Checksum.cs @@ -21,13 +21,10 @@ public bool TryGetStateChecksums([NotNullWhen(true)] out ProjectStateChecksums? public Task GetStateChecksumsAsync(CancellationToken cancellationToken) => LazyChecksums.GetValueAsync(cancellationToken); - public Task GetChecksumAsync(CancellationToken cancellationToken) + public async ValueTask GetChecksumAsync(CancellationToken cancellationToken) { - return SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync( - static (lazyChecksums, cancellationToken) => new ValueTask(lazyChecksums.GetValueAsync(cancellationToken)), - static (projectStateChecksums, _) => projectStateChecksums.Checksum, - LazyChecksums, - cancellationToken).AsTask(); + var projectStateChecksums = await this.LazyChecksums.GetValueAsync(cancellationToken).ConfigureAwait(false); + return projectStateChecksums.Checksum; } public Checksum GetParseOptionsChecksum() diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs index d1e1892135a13..ab9bfa465da47 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs @@ -97,18 +97,13 @@ public bool TryGetTextVersion(out VersionStamp version) public bool TryGetTextAndVersion([NotNullWhen(true)] out TextAndVersion? textAndVersion) => TextAndVersionSource.TryGetValue(LoadTextOptions, out textAndVersion); - public ValueTask GetTextAsync(CancellationToken cancellationToken) + public async ValueTask GetTextAsync(CancellationToken cancellationToken) { if (TryGetText(out var text)) - { - return new ValueTask(text); - } + return text; - return SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync( - static (self, cancellationToken) => self.GetTextAndVersionAsync(cancellationToken), - static (textAndVersion, _) => textAndVersion.Text, - this, - cancellationToken); + var textAndVersion = await GetTextAndVersionAsync(cancellationToken).ConfigureAwait(false); + return textAndVersion.Text; } public SourceText GetTextSynchronously(CancellationToken cancellationToken) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState_Checksum.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState_Checksum.cs index 6f80aabca17cc..4420aba052feb 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState_Checksum.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState_Checksum.cs @@ -21,13 +21,10 @@ public bool TryGetStateChecksums([NotNullWhen(returnValue: true)] out DocumentSt public Task GetStateChecksumsAsync(CancellationToken cancellationToken) => _lazyChecksums.GetValueAsync(cancellationToken); - public Task GetChecksumAsync(CancellationToken cancellationToken) + public async ValueTask GetChecksumAsync(CancellationToken cancellationToken) { - return SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync( - static (lazyChecksums, cancellationToken) => new ValueTask(lazyChecksums.GetValueAsync(cancellationToken)), - static (documentStateChecksums, _) => documentStateChecksums.Checksum, - _lazyChecksums, - cancellationToken).AsTask(); + var documentStateChecksums = await _lazyChecksums.GetValueAsync(cancellationToken).ConfigureAwait(false); + return documentStateChecksums.Checksum; } private async Task ComputeChecksumsAsync(CancellationToken cancellationToken) diff --git a/src/Workspaces/CoreTest/UtilityTest/SpecializedTasksTests.cs b/src/Workspaces/CoreTest/UtilityTest/SpecializedTasksTests.cs index ed9cd1bde4d7b..9bbcb055b71b8 100644 --- a/src/Workspaces/CoreTest/UtilityTest/SpecializedTasksTests.cs +++ b/src/Workspaces/CoreTest/UtilityTest/SpecializedTasksTests.cs @@ -12,8 +12,6 @@ using Roslyn.Utilities; using Xunit; -#pragma warning disable IDE0039 // Use local function - namespace Microsoft.CodeAnalysis.UnitTests; [SuppressMessage("Usage", "VSTHRD104:Offer async methods", Justification = "This class tests specific behavior of tasks.")] @@ -69,489 +67,4 @@ public void WhenAll_NotYetCompleted() Debug.Assert(whenAll.IsCompleted); Assert.Equal((int[])[0], whenAll.Result); } - - [Fact] - public void Transform_ArgumentValidation() - { - Func> func = (_, _) => new(new IntermediateType()); - Func transform = (_, _) => new(); - var arg = new StateType(); - var cancellationToken = new CancellationToken(canceled: false); - -#pragma warning disable CA2012 // Use ValueTasks correctly (the instance is never created) - Assert.Throws("func", () => SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync(null!, transform, arg, cancellationToken)); - Assert.Throws("transform", () => SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync(func, null!, arg, cancellationToken)); -#pragma warning restore CA2012 // Use ValueTasks correctly - } - - [Fact] - public void Transform_SyncCompletedFunction_CompletedTransform() - { - Func> func = (_, _) => new(new IntermediateType()); - Func transform = (_, _) => new(); - var arg = new StateType(); - var cancellationToken = new CancellationToken(canceled: false); - - var task = SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync(func, transform, arg, cancellationToken).Preserve(); - Assert.True(task.IsCompletedSuccessfully); - Assert.NotNull(task.Result); - } - - [Fact] - public void Transform_SyncCompletedFunction_CancellationRequested_IgnoresTransform() - { - using var cts = new CancellationTokenSource(); - cts.Cancel(); - - var executedTransform = false; - - var cancellationToken = cts.Token; - Func> func = (_, _) => new(new IntermediateType()); - Func transform = (_, _) => - { - executedTransform = true; - return new ResultType(); - }; - var arg = new StateType(); - - var task = SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync(func, transform, arg, cancellationToken).Preserve(); - Assert.True(task.IsCanceled); - var exception = Assert.Throws(() => task.Result); - Assert.Equal(cancellationToken, exception.CancellationToken); - Assert.False(executedTransform); - } - - [Fact] - public async Task Transform_AsyncCompletedFunction_CompletedTransform() - { - var gate = new ManualResetEventSlim(); - Func> func = async (_, _) => - { - await Task.Yield(); - gate.Wait(CancellationToken.None); - return new IntermediateType(); - }; - Func transform = (_, _) => new(); - var arg = new StateType(); - var cancellationToken = new CancellationToken(canceled: false); - - var task = SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync(func, transform, arg, cancellationToken).Preserve(); - Assert.False(task.IsCompleted); - - gate.Set(); - Assert.NotNull(await task); - } - - [Fact] - public async Task Transform_AsyncCompletedFunction_CancellationRequested_IgnoresTransform() - { - using var cts = new CancellationTokenSource(); - cts.Cancel(); - - var executedTransform = false; - - var cancellationToken = cts.Token; - var gate = new ManualResetEventSlim(); - Func> func = async (_, _) => - { - await Task.Yield(); - gate.Wait(CancellationToken.None); - return new IntermediateType(); - }; - Func transform = (_, _) => - { - executedTransform = true; - return new ResultType(); - }; - var arg = new StateType(); - - var task = SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync(func, transform, arg, cancellationToken).Preserve(); - Assert.False(task.IsCompleted); - - gate.Set(); - var exception = await Assert.ThrowsAsync(async () => await task); - Assert.Equal(cancellationToken, exception.CancellationToken); - Assert.False(executedTransform); - } - - [Fact] - public void Transform_SyncCanceledFunction_IgnoresTransform() - { - using var cts = new CancellationTokenSource(); - cts.Cancel(); - - var executedTransform = false; - - var cancellationToken = cts.Token; - Func> func = (_, _) => new(Task.FromCanceled(cancellationToken)); - Func transform = (_, _) => - { - executedTransform = true; - return new ResultType(); - }; - var arg = new StateType(); - - var task = SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync(func, transform, arg, cancellationToken).Preserve(); - Assert.True(task.IsCanceled); - var exception = Assert.Throws(() => task.Result); - Assert.Equal(cancellationToken, exception.CancellationToken); - Assert.False(executedTransform); - } - - [Fact] - public async Task Transform_AsyncCanceledFunction_IgnoresTransform() - { - using var cts = new CancellationTokenSource(); - cts.Cancel(); - - var executedTransform = false; - - var cancellationToken = cts.Token; - var gate = new ManualResetEventSlim(); - Func> func = async (_, _) => - { - await Task.Yield(); - gate.Wait(CancellationToken.None); - cts.Token.ThrowIfCancellationRequested(); - throw ExceptionUtilities.Unreachable(); - }; - Func transform = (_, _) => - { - executedTransform = true; - return new ResultType(); - }; - var arg = new StateType(); - - var task = SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync(func, transform, arg, cancellationToken).Preserve(); - Assert.False(task.IsCompleted); - - gate.Set(); - var exception = await Assert.ThrowsAsync(async () => await task); - Assert.Equal(cancellationToken, exception.CancellationToken); - Assert.False(executedTransform); - } - - [Fact] - public void Transform_SyncCanceledFunction_NotRequested_IgnoresTransform() - { - using var unexpectedCts = new CancellationTokenSource(); - unexpectedCts.Cancel(); - - var executedTransform = false; - - var cancellationToken = new CancellationToken(canceled: false); - Func> func = (_, _) => new(Task.FromCanceled(unexpectedCts.Token)); - Func transform = (_, _) => - { - executedTransform = true; - return new ResultType(); - }; - var arg = new StateType(); - - var task = SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync(func, transform, arg, cancellationToken).Preserve(); - Assert.True(task.IsCanceled); - var exception = Assert.Throws(() => task.Result); - - // ⚠ Due to the way cancellation is handled in ContinueWith, the resulting exception fails to preserve the - // cancellation token applied when the intermediate task was cancelled. - Assert.Equal(cancellationToken, exception.CancellationToken); - Assert.False(executedTransform); - } - - [Fact] - public async Task Transform_AsyncCanceledFunction_NotRequested_IgnoresTransform() - { - using var unexpectedCts = new CancellationTokenSource(); - unexpectedCts.Cancel(); - - var executedTransform = false; - - var cancellationToken = new CancellationToken(canceled: false); - var gate = new ManualResetEventSlim(); - Func> func = async (_, _) => - { - await Task.Yield(); - gate.Wait(CancellationToken.None); - unexpectedCts.Token.ThrowIfCancellationRequested(); - throw ExceptionUtilities.Unreachable(); - }; - Func transform = (_, _) => - { - executedTransform = true; - return new ResultType(); - }; - var arg = new StateType(); - - var task = SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync(func, transform, arg, cancellationToken).Preserve(); - Assert.False(task.IsCompleted); - - gate.Set(); - var exception = await Assert.ThrowsAsync(async () => await task); - Assert.True(task.IsCanceled); - - // ⚠ Due to the way cancellation is handled in ContinueWith, the resulting exception fails to preserve the - // cancellation token applied when the intermediate task was cancelled. - Assert.Equal(cancellationToken, exception.CancellationToken); - Assert.False(executedTransform); - } - - [Fact] - public void Transform_SyncCanceledFunction_MismatchToken_IgnoresTransform() - { - using var cts = new CancellationTokenSource(); - cts.Cancel(); - - using var unexpectedCts = new CancellationTokenSource(); - unexpectedCts.Cancel(); - - var executedTransform = false; - - var cancellationToken = cts.Token; - Func> func = (_, _) => new(Task.FromCanceled(unexpectedCts.Token)); - Func transform = (_, _) => - { - executedTransform = true; - return new ResultType(); - }; - var arg = new StateType(); - - var task = SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync(func, transform, arg, cancellationToken).Preserve(); - Assert.True(task.IsCanceled); - var exception = Assert.Throws(() => task.Result); - Assert.Equal(cancellationToken, exception.CancellationToken); - Assert.False(executedTransform); - } - - [Fact] - public async Task Transform_AsyncCanceledFunction_MismatchToken_IgnoresTransform() - { - using var cts = new CancellationTokenSource(); - cts.Cancel(); - - using var unexpectedCts = new CancellationTokenSource(); - unexpectedCts.Cancel(); - - var executedTransform = false; - - var cancellationToken = cts.Token; - var gate = new ManualResetEventSlim(); - Func> func = async (_, _) => - { - await Task.Yield(); - gate.Wait(CancellationToken.None); - unexpectedCts.Token.ThrowIfCancellationRequested(); - throw ExceptionUtilities.Unreachable(); - }; - Func transform = (_, _) => - { - executedTransform = true; - return new ResultType(); - }; - var arg = new StateType(); - - var task = SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync(func, transform, arg, cancellationToken).Preserve(); - Assert.False(task.IsCompleted); - - gate.Set(); - var exception = await Assert.ThrowsAsync(async () => await task); - Assert.True(task.IsCanceled); - Assert.Equal(cancellationToken, exception.CancellationToken); - Assert.False(executedTransform); - } - - [Fact] - public void Transform_SyncDirectFaultedFunction_IgnoresTransform() - { - var executedTransform = false; - - var fault = ExceptionUtilities.Unreachable(); - var cancellationToken = new CancellationToken(canceled: false); - Func> func = (_, _) => throw fault; - Func transform = (_, _) => - { - executedTransform = true; - return new ResultType(); - }; - var arg = new StateType(); - -#pragma warning disable CA2012 // Use ValueTasks correctly (the instance is never created) - var exception = Assert.Throws(() => SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync(func, transform, arg, cancellationToken)); -#pragma warning restore CA2012 // Use ValueTasks correctly - Assert.Same(fault, exception); - Assert.False(executedTransform); - } - - [Fact] - public void Transform_SyncFaultedFunction_IgnoresTransform() - { - var executedTransform = false; - - var fault = ExceptionUtilities.Unreachable(); - var cancellationToken = new CancellationToken(canceled: false); - Func> func = (_, _) => new(Task.FromException(fault)); - Func transform = (_, _) => - { - executedTransform = true; - return new ResultType(); - }; - var arg = new StateType(); - - var task = SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync(func, transform, arg, cancellationToken).Preserve(); - Assert.True(task.IsFaulted); - var exception = Assert.Throws(() => task.Result); - Assert.Same(fault, exception); - Assert.False(executedTransform); - } - - [Fact] - public async Task Transform_AsyncFaultedFunction_IgnoresTransform() - { - var executedTransform = false; - - var fault = ExceptionUtilities.Unreachable(); - var cancellationToken = new CancellationToken(canceled: false); - var gate = new ManualResetEventSlim(); - Func> func = async (_, _) => - { - await Task.Yield(); - gate.Wait(CancellationToken.None); - throw fault; - }; - Func transform = (_, _) => - { - executedTransform = true; - return new ResultType(); - }; - var arg = new StateType(); - - var task = SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync(func, transform, arg, cancellationToken).Preserve(); - Assert.False(task.IsCompleted); - - gate.Set(); - var exception = await Assert.ThrowsAsync(() => task.AsTask()); - Assert.Same(fault, exception); - Assert.False(executedTransform); - } - - [Fact] - public void Transform_SyncDirectFaultedFunction_CancellationRequested_IgnoresTransform() - { - using var cts = new CancellationTokenSource(); - cts.Cancel(); - - var executedTransform = false; - - var fault = ExceptionUtilities.Unreachable(); - var cancellationToken = cts.Token; - Func> func = (_, _) => throw fault; - Func transform = (_, _) => - { - executedTransform = true; - return new ResultType(); - }; - var arg = new StateType(); - -#pragma warning disable CA2012 // Use ValueTasks correctly (the instance is never created) - var exception = Assert.Throws(() => SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync(func, transform, arg, cancellationToken)); -#pragma warning restore CA2012 // Use ValueTasks correctly - Assert.Same(fault, exception); - Assert.False(executedTransform); - } - - [Fact] - public void Transform_SyncFaultedFunction_CancellationRequested_IgnoresTransform() - { - using var cts = new CancellationTokenSource(); - cts.Cancel(); - - var executedTransform = false; - - var fault = ExceptionUtilities.Unreachable(); - var cancellationToken = cts.Token; - Func> func = (_, _) => new(Task.FromException(fault)); - Func transform = (_, _) => - { - executedTransform = true; - return new ResultType(); - }; - var arg = new StateType(); - - var task = SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync(func, transform, arg, cancellationToken).Preserve(); - Assert.True(task.IsCanceled); - var exception = Assert.Throws(() => task.Result); - Assert.Equal(cancellationToken, exception.CancellationToken); - Assert.False(executedTransform); - } - - [Fact] - public async Task Transform_AsyncFaultedFunction_CancellationRequested_IgnoresTransform() - { - using var cts = new CancellationTokenSource(); - cts.Cancel(); - - var executedTransform = false; - - var fault = ExceptionUtilities.Unreachable(); - var cancellationToken = cts.Token; - var gate = new ManualResetEventSlim(); - Func> func = async (_, _) => - { - await Task.Yield(); - gate.Wait(CancellationToken.None); - throw fault; - }; - Func transform = (_, _) => - { - executedTransform = true; - return new ResultType(); - }; - var arg = new StateType(); - - var task = SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync(func, transform, arg, cancellationToken).Preserve(); - Assert.False(task.IsCompleted); - - gate.Set(); - var exception = await Assert.ThrowsAsync(() => task.AsTask()); - Assert.True(task.IsCanceled); - Assert.Equal(cancellationToken, exception.CancellationToken); - Assert.False(executedTransform); - } - - [Fact] - public void Transform_SyncCompletedFunction_FaultedTransform() - { - var fault = ExceptionUtilities.Unreachable(); - Func> func = (_, _) => new(new IntermediateType()); - Func transform = (_, _) => throw fault; - var arg = new StateType(); - var cancellationToken = new CancellationToken(canceled: false); - -#pragma warning disable CA2012 // Use ValueTasks correctly (the instance is never created) - var exception = Assert.Throws(() => SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync(func, transform, arg, cancellationToken)); -#pragma warning restore CA2012 // Use ValueTasks correctly - Assert.Same(fault, exception); - } - - [Fact] - public async Task Transform_AsyncCompletedFunction_FaultedTransform() - { - var fault = ExceptionUtilities.Unreachable(); - var gate = new ManualResetEventSlim(); - Func> func = async (_, _) => - { - await Task.Yield(); - gate.Wait(CancellationToken.None); - return new IntermediateType(); - }; - Func transform = (_, _) => throw fault; - var arg = new StateType(); - var cancellationToken = new CancellationToken(canceled: false); - - var task = SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync(func, transform, arg, cancellationToken).Preserve(); - Assert.False(task.IsCompleted); - - gate.Set(); - var exception = await Assert.ThrowsAsync(() => task.AsTask()); - Assert.Same(fault, exception); - } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SpecializedTasks.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SpecializedTasks.cs index 4d8a12875fafb..04b3f114228ad 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SpecializedTasks.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SpecializedTasks.cs @@ -95,90 +95,6 @@ public static async ValueTask> WhenAll(this IRe return result.MoveToImmutable(); } - /// - /// This helper method provides semantics equivalent to the following, but avoids throwing an intermediate - /// in the case where the asynchronous operation is cancelled. - /// - /// MethodAsync(TArg arg, CancellationToken cancellationToken) - /// { - /// var intermediate = await func(arg, cancellationToken).ConfigureAwait(false); - /// return transform(intermediate); - /// } - /// ]]> - /// - /// - /// This helper method is only intended for use in cases where profiling reveals substantial overhead related to - /// cancellation processing. - /// - /// The type of a state variable to pass to and . - /// The type of intermediate result produced by . - /// The type of result produced by . - /// The intermediate asynchronous operation. - /// The synchronous transformation to apply to the result of . - /// The state to pass to and . - /// The that the operation will observe. - public static ValueTask TransformWithoutIntermediateCancellationExceptionAsync( - Func> func, - Func transform, - TArg arg, - CancellationToken cancellationToken) - { - if (func is null) - throw new ArgumentNullException(nameof(func)); - if (transform is null) - throw new ArgumentNullException(nameof(transform)); - - var intermediateResult = func(arg, cancellationToken); - if (intermediateResult.IsCompletedSuccessfully) - { - // Synchronous fast path if 'func' completes synchronously - var result = intermediateResult.Result; - if (cancellationToken.IsCancellationRequested) - return new ValueTask(Task.FromCanceled(cancellationToken)); - - return new ValueTask(transform(result, arg)); - } - else if (intermediateResult.IsCanceled && cancellationToken.IsCancellationRequested) - { - // Synchronous fast path if 'func' cancels synchronously - return new ValueTask(Task.FromCanceled(cancellationToken)); - } - else - { - // Asynchronous fallback path - return UnwrapAndTransformAsync(intermediateResult, transform, arg, cancellationToken); - } - - static ValueTask UnwrapAndTransformAsync(ValueTask intermediateResult, Func transform, TArg arg, CancellationToken cancellationToken) - { - // Apply the transformation function once a result is available. The behavior depends on the final - // status of 'intermediateResult' and the 'cancellationToken'. - // - // | 'intermediateResult' | 'cancellationToken' | Behavior | - // | -------------------------- | ------------------- | ---------------------------------------- | - // | Ran to completion | Not cancelled | Apply transform | - // | Ran to completion | Cancelled | Cancel result without applying transform | - // | Cancelled (matching token) | Cancelled | Cancel result without applying transform | - // | Cancelled (mismatch token) | Not cancelled | Cancel result without applying transform | - // | Cancelled (mismatch token) | Cancelled | Cancel result without applying transform | - // | Direct fault¹ | Not cancelled | Directly fault (exception is not caught) | - // | Direct fault¹ | Cancelled | Directly fault (exception is not caught) | - // | Indirect fault | Not cancelled | Fault result without applying transform | - // | Indirect fault | Cancelled | Cancel result without applying transform | - // - // ¹ Direct faults are exceptions thrown from 'func' prior to returning a ValueTask - // instances. Indirect faults are exceptions captured by return an instance of - // ValueTask which (immediately or eventually) transitions to the faulted state. The - // direct fault behavior is currently handled without calling UnwrapAndTransformAsync. - return new ValueTask(intermediateResult.AsTask().ContinueWith( - task => transform(task.GetAwaiter().GetResult(), arg), - cancellationToken, - TaskContinuationOptions.LazyCancellation | TaskContinuationOptions.NotOnCanceled | TaskContinuationOptions.ExecuteSynchronously, - TaskScheduler.Default)); - } - } - private static class EmptyTasks { public static readonly Task Default = Task.FromResult(default);