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.
Add AsyncValueTaskMethodBuilder in support of Roslyn feature
The C# compiler just merged a feature to allow arbitrary Task-like types in the return position of async methods. In support of that, this commit adds the necessary builder to allow ```ValueTask<T>``` to be used in this way.
- Loading branch information
1 parent
dc9c565
commit 0458fc9
Showing
5 changed files
with
249 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
107 changes: 107 additions & 0 deletions
107
...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,107 @@ | ||
// 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 | ||
{ | ||
// We can't provide the same semantics as AsyncTaskMethodBuilder with regards to | ||
// ExecutionContext as we don't have access to any of the relevant methods. We also can't | ||
// call _methodBuilder.Start, as then we'll always end up creating a Task<T>. At the moment the | ||
// only solution is to skip the ExecutionContext barrier that we'd want to put in place. | ||
stateMachine.MoveNext(); | ||
} | ||
|
||
/// <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
131 changes: 131 additions & 0 deletions
131
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,131 @@ | ||
// 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()); | ||
} | ||
|
||
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