This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 4.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #10201 from stephentoub/valuetask_builder
Add AsyncValueTaskMethodBuilder in support of C# feature
- Loading branch information
Showing
5 changed files
with
279 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
103 changes: 103 additions & 0 deletions
103
...ading.Tasks.Extensions/src/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
// 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. | ||
|
||
using System.Runtime.InteropServices; | ||
using System.Security; | ||
using System.Threading.Tasks; | ||
|
||
namespace System.Runtime.CompilerServices | ||
{ | ||
/// <summary>Represents a builder for asynchronous methods that returns a <see cref="ValueTask{TResult}"/>.</summary> | ||
/// <typeparam name="TResult">The type of the result.</typeparam> | ||
[StructLayout(LayoutKind.Auto)] | ||
public struct AsyncValueTaskMethodBuilder<TResult> | ||
{ | ||
/// <summary>The <see cref="AsyncTaskMethodBuilder{TResult}"/> to which most operations are delegated.</summary> | ||
private AsyncTaskMethodBuilder<TResult> _methodBuilder; | ||
/// <summary>The result for this builder, if it's completed before any awaits occur.</summary> | ||
private TResult _result; | ||
/// <summary>true if <see cref="_result"/> contains the synchronous result for the async method; otherwise, false.</summary> | ||
private bool _haveResult; | ||
/// <summary>true if the builder should be used for setting/getting the result; otherwise, false.</summary> | ||
private bool _useBuilder; | ||
|
||
/// <summary>Creates an instance of the <see cref="AsyncValueTaskMethodBuilder{TResult}"/> struct.</summary> | ||
/// <returns>The initialized instance.</returns> | ||
public static AsyncValueTaskMethodBuilder<TResult> Create() => | ||
new AsyncValueTaskMethodBuilder<TResult>() { _methodBuilder = AsyncTaskMethodBuilder<TResult>.Create() }; | ||
|
||
/// <summary>Begins running the builder with the associated state machine.</summary> | ||
/// <typeparam name="TStateMachine">The type of the state machine.</typeparam> | ||
/// <param name="stateMachine">The state machine instance, passed by reference.</param> | ||
public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine | ||
{ | ||
_methodBuilder.Start(ref stateMachine); // will provide the right ExecutionContext semantics | ||
} | ||
|
||
/// <summary>Associates the builder with the specified state machine.</summary> | ||
/// <param name="stateMachine">The state machine instance to associate with the builder.</param> | ||
public void SetStateMachine(IAsyncStateMachine stateMachine) => _methodBuilder.SetStateMachine(stateMachine); | ||
|
||
/// <summary>Marks the task as successfully completed.</summary> | ||
/// <param name="result">The result to use to complete the task.</param> | ||
public void SetResult(TResult result) | ||
{ | ||
if (_useBuilder) | ||
{ | ||
_methodBuilder.SetResult(result); | ||
} | ||
else | ||
{ | ||
_result = result; | ||
_haveResult = true; | ||
} | ||
} | ||
|
||
/// <summary>Marks the task as failed and binds the specified exception to the task.</summary> | ||
/// <param name="exception">The exception to bind to the task.</param> | ||
public void SetException(Exception exception) => _methodBuilder.SetException(exception); | ||
|
||
/// <summary>Gets the task for this builder.</summary> | ||
public ValueTask<TResult> Task | ||
{ | ||
get | ||
{ | ||
if (_haveResult) | ||
{ | ||
return new ValueTask<TResult>(_result); | ||
} | ||
else | ||
{ | ||
_useBuilder = true; | ||
return new ValueTask<TResult>(_methodBuilder.Task); | ||
} | ||
} | ||
} | ||
|
||
/// <summary>Schedules the state machine to proceed to the next action when the specified awaiter completes.</summary> | ||
/// <typeparam name="TAwaiter">The type of the awaiter.</typeparam> | ||
/// <typeparam name="TStateMachine">The type of the state machine.</typeparam> | ||
/// <param name="awaiter">the awaiter</param> | ||
/// <param name="stateMachine">The state machine.</param> | ||
public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) | ||
where TAwaiter : INotifyCompletion | ||
where TStateMachine : IAsyncStateMachine | ||
{ | ||
_methodBuilder.AwaitOnCompleted(ref awaiter, ref stateMachine); | ||
} | ||
|
||
/// <summary>Schedules the state machine to proceed to the next action when the specified awaiter completes.</summary> | ||
/// <typeparam name="TAwaiter">The type of the awaiter.</typeparam> | ||
/// <typeparam name="TStateMachine">The type of the state machine.</typeparam> | ||
/// <param name="awaiter">the awaiter</param> | ||
/// <param name="stateMachine">The state machine.</param> | ||
[SecuritySafeCritical] | ||
public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) | ||
where TAwaiter : ICriticalNotifyCompletion | ||
where TStateMachine : IAsyncStateMachine | ||
{ | ||
_methodBuilder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
165 changes: 165 additions & 0 deletions
165
src/System.Threading.Tasks.Extensions/tests/AsyncValueTaskMethodBuilderTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
// 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. | ||
|
||
using System.Runtime.CompilerServices; | ||
using Xunit; | ||
|
||
namespace System.Threading.Tasks.Tests | ||
{ | ||
public class AsyncValueTaskMethodBuilderTests | ||
{ | ||
[Fact] | ||
public void Create_ReturnsDefaultInstance() | ||
{ | ||
AsyncValueTaskMethodBuilder<int> b = ValueTask<int>.CreateAsyncMethodBuilder(); | ||
Assert.Equal(default(AsyncValueTaskMethodBuilder<int>), b); // implementation detail being verified | ||
} | ||
|
||
[Fact] | ||
public void SetResult_BeforeAccessTask_ValueTaskContainsValue() | ||
{ | ||
AsyncValueTaskMethodBuilder<int> b = ValueTask<int>.CreateAsyncMethodBuilder(); | ||
b.SetResult(42); | ||
ValueTask<int> vt = b.Task; | ||
Assert.True(vt.IsCompletedSuccessfully); | ||
Assert.NotSame(vt.AsTask(), vt.AsTask()); // will be different if completed synchronously | ||
Assert.Equal(42, vt.Result); | ||
} | ||
|
||
[Fact] | ||
public void SetResult_AfterAccessTask_ValueTaskContainsValue() | ||
{ | ||
AsyncValueTaskMethodBuilder<int> b = ValueTask<int>.CreateAsyncMethodBuilder(); | ||
ValueTask<int> vt = b.Task; | ||
b.SetResult(42); | ||
Assert.True(vt.IsCompletedSuccessfully); | ||
Assert.Same(vt.AsTask(), vt.AsTask()); // will be safe if completed asynchronously | ||
Assert.Equal(42, vt.Result); | ||
} | ||
|
||
[Fact] | ||
public void SetException_BeforeAccessTask_FaultsTask() | ||
{ | ||
AsyncValueTaskMethodBuilder<int> b = ValueTask<int>.CreateAsyncMethodBuilder(); | ||
var e = new FormatException(); | ||
b.SetException(e); | ||
ValueTask<int> vt = b.Task; | ||
Assert.True(vt.IsFaulted); | ||
Assert.Same(e, Assert.Throws<FormatException>(() => vt.GetAwaiter().GetResult())); | ||
} | ||
|
||
[Fact] | ||
public void SetException_AfterAccessTask_FaultsTask() | ||
{ | ||
AsyncValueTaskMethodBuilder<int> b = ValueTask<int>.CreateAsyncMethodBuilder(); | ||
var e = new FormatException(); | ||
ValueTask<int> vt = b.Task; | ||
b.SetException(e); | ||
Assert.True(vt.IsFaulted); | ||
Assert.Same(e, Assert.Throws<FormatException>(() => vt.GetAwaiter().GetResult())); | ||
} | ||
|
||
[Fact] | ||
public void SetException_OperationCanceledException_CancelsTask() | ||
{ | ||
AsyncValueTaskMethodBuilder<int> b = ValueTask<int>.CreateAsyncMethodBuilder(); | ||
var e = new OperationCanceledException(); | ||
ValueTask<int> vt = b.Task; | ||
b.SetException(e); | ||
Assert.True(vt.IsCanceled); | ||
Assert.Same(e, Assert.Throws<OperationCanceledException>(() => vt.GetAwaiter().GetResult())); | ||
} | ||
|
||
[Fact] | ||
public void Start_InvokesMoveNext() | ||
{ | ||
AsyncValueTaskMethodBuilder<int> b = ValueTask<int>.CreateAsyncMethodBuilder(); | ||
int invokes = 0; | ||
var dsm = new DelegateStateMachine { MoveNextDelegate = () => invokes++ }; | ||
b.Start(ref dsm); | ||
Assert.Equal(1, invokes); | ||
} | ||
|
||
[Theory] | ||
[InlineData(false)] | ||
[InlineData(true)] | ||
public async Task AwaitOnCompleted_InvokesStateMachineMethods(bool awaitUnsafe) | ||
{ | ||
AsyncValueTaskMethodBuilder<int> b = ValueTask<int>.CreateAsyncMethodBuilder(); | ||
var ignored = b.Task; | ||
|
||
var callbackCompleted = new TaskCompletionSource<bool>(); | ||
IAsyncStateMachine foundSm = null; | ||
var dsm = new DelegateStateMachine | ||
{ | ||
MoveNextDelegate = () => callbackCompleted.SetResult(true), | ||
SetStateMachineDelegate = sm => foundSm = sm | ||
}; | ||
|
||
TaskAwaiter t = Task.CompletedTask.GetAwaiter(); | ||
if (awaitUnsafe) | ||
{ | ||
b.AwaitUnsafeOnCompleted(ref t, ref dsm); | ||
} | ||
else | ||
{ | ||
b.AwaitOnCompleted(ref t, ref dsm); | ||
} | ||
|
||
await callbackCompleted.Task; | ||
Assert.Equal(dsm, foundSm); | ||
} | ||
|
||
[Fact] | ||
public void SetStateMachine_InvalidArgument_ThrowsException() | ||
{ | ||
AsyncValueTaskMethodBuilder<int> b = ValueTask<int>.CreateAsyncMethodBuilder(); | ||
Assert.Throws<ArgumentNullException>("stateMachine", () => b.SetStateMachine(null)); | ||
b.SetStateMachine(new DelegateStateMachine()); | ||
} | ||
|
||
[Fact] | ||
public void Start_ExecutionContextChangesInMoveNextDontFlowOut() | ||
{ | ||
var al = new AsyncLocal<int> { Value = 0 }; | ||
int calls = 0; | ||
|
||
var dsm = new DelegateStateMachine | ||
{ | ||
MoveNextDelegate = () => | ||
{ | ||
al.Value++; | ||
calls++; | ||
} | ||
}; | ||
|
||
dsm.MoveNext(); | ||
Assert.Equal(1, al.Value); | ||
Assert.Equal(1, calls); | ||
|
||
dsm.MoveNext(); | ||
Assert.Equal(2, al.Value); | ||
Assert.Equal(2, calls); | ||
|
||
AsyncValueTaskMethodBuilder<int> b = ValueTask<int>.CreateAsyncMethodBuilder(); | ||
b.Start(ref dsm); | ||
Assert.Equal(2, al.Value); // change should not be visible | ||
Assert.Equal(3, calls); | ||
|
||
// Make sure we've not caused the Task to be allocated | ||
b.SetResult(42); | ||
ValueTask<int> vt = b.Task; | ||
Assert.NotSame(vt.AsTask(), vt.AsTask()); | ||
} | ||
|
||
private struct DelegateStateMachine : IAsyncStateMachine | ||
{ | ||
internal Action MoveNextDelegate; | ||
public void MoveNext() => MoveNextDelegate?.Invoke(); | ||
|
||
internal Action<IAsyncStateMachine> SetStateMachineDelegate; | ||
public void SetStateMachine(IAsyncStateMachine stateMachine) => SetStateMachineDelegate?.Invoke(stateMachine); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters