Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

Add AsyncValueTaskMethodBuilder in support of C# feature #10201

Merged
merged 2 commits into from Jul 22, 2016

Conversation

stephentoub
Copy link
Member

@stephentoub stephentoub commented Jul 20, 2016

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.

I've not yet tried this with compiler bits.

cc: @cston, @ljw1004, @mellinoe, @terrajobst, @jaredpar
Related to dotnet/roslyn#12518

@ljw1004
Copy link

ljw1004 commented Jul 20, 2016

LGTM

@stephentoub
Copy link
Member Author

Thanks, Lucian. Anyone else want to review before I merge this?

@@ -4,7 +4,7 @@
<PropertyGroup>
<ProjectGuid>{F24D3391-2928-4E83-AADE-B34423498750}</ProjectGuid>
<AssemblyName>System.Threading.Tasks.Extensions</AssemblyName>
<AssemblyVersion>4.0.1.0</AssemblyVersion>
<AssemblyVersion>4.1.0.0</AssemblyVersion>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this vs. 4.0.2?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been told that adding APIs requires bumping the minor version. @weshaggard, is that correct?

@cston
Copy link
Member

cston commented Jul 21, 2016

LGTM

public void Create_ReturnsDefaultInstance()
{
AsyncValueTaskMethodBuilder<int> b = ValueTask<int>.CreateAsyncMethodBuilder();
Assert.Equal(default(AsyncValueTaskMethodBuilder<int>), b);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a bit confusing to me at first, because AsyncValueTaskMethodBuilder.Create does assign to its _methodBuilder field. But it turns out that it assigns it to default anyways (or technically calls a method returning default and assigns that). I guess we would need to change this test if that ended up returning something else? (There's a comment suggesting we might here)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we would need to change this test if that ended up returning something else?

Yup

@mellinoe
Copy link
Contributor

LGTM

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.
// 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();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ericeil, not to block this PR, but for subsequently, any thoughts about what to do here?

Outside of mscorlib, the options are relatively limited. If we were to bump up to the latest .NET Standard version, we could use ExecutionContext.Capture/Run to simulate what's being done internally in AsyncTaskMethodBuilder.Start, but that seems unfortunately expensive, at least for desktop, and it means either we don't support earlier standards, or we have multiple builds and end up with different behaviors based on which standard you're targeting.

I'm tempted to leave this as a wart for now. The impact as you know will be that any ExecutionContext-related changes that occur in the async method prior to it yielding will be visible to the caller, but none of the options seem particularly good.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you just replace the call to MoveNext with this?

AsyncTaskMethodBuilder.Create().Start(ref stateMachine);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At least in the CoreCLR implementation, that looks like it would do the trick without allocating unnecessarily.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you just replace the call to MoveNext with this?

I'd convinced myself that the answer was "no", that doing so would cause it to access the AsyncTaskMethodBuilder's Task and force the allocation that all of this is trying to avoid. But looking at it again, Start's whole purpose is really just to wrap the stateMachine's invocation exactly the way we want to, so it should be fine. I'll verify, but... I think this works :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, Eric! Fixed.

@stephentoub
Copy link
Member Author

Thanks, all.

@stephentoub stephentoub merged commit ce3e10f into dotnet:master Jul 22, 2016
@stephentoub stephentoub deleted the valuetask_builder branch July 22, 2016 00:03
/// <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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, maybe these two bools could be aggregated into an int instead? If I remember correctly, bools actually take up 4 bytes since the CLR pads them to align field accesses. Maybe it could be changed to a single field (byte, short, or int) that uses flags.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Making these bools into an int will make it worse if the TResult is two bytes or less; on 32-bit, the TResult and the two bools can then be placed in the same word. Other than that, on both 32-bit and 64-bit, size-wise it's the same, and using an int would just make the code more complicated.

@karelz karelz modified the milestone: 1.1.0 Dec 3, 2016
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
9 participants