Properly handle generic ValueTask await adapter#4640
Conversation
manfred-brands
left a comment
There was a problem hiding this comment.
Thanks for doing this.
Looks good.
|
@RenderMichael Any idea why the tests after 3 hours are still running? |
|
Ah never mind, I was completely misinterpreting those results. I guess the failure was at an unfortunate location where it caused an infinite wait instead of an exception bubbling up. |
|
Good there are tests then! @RenderMichael Btw, are there enough tests to cover the changes done here ? |
|
@OsirisTerje I’m happy to add a unit test here, note that while there’s no explicit test coverage, as you can see from the CI failures, this was implicitly tested by the rest of the suite. |
|
@jnm2 wrote the AsyncAdapters back in 2018, and he added a series of tests, AsyncExecutionApiAdapterTests, AwaitableReturnTypeTests, and more - search for "await" in the test explorer. @jnm2 might explain more here, but I got a feeling we should have added more tests here when ValueTask was introduced/changed. And the old #2774 is still open. @RenderMichael Nice if you can have a look at the tests I mention above, and see if you could add to those. The issues related to ValueTask are old, so this is not new, but when we start changing things, that's when I at least start to be a bit worried :-) About implicitly tests, I am not sure if that was implicit or a side-effect, if it was implicit, it should have shown up in the coverage. UPDATE: I tested on the old code, not your PR, so this is not relevant :-) Sorry. @stevenaw @manfred-brands Comments ? |
|
@RenderMichael Can you share the code you use to verify that it is working. I am trying with the repro code for the #4588 issue and can't get it to go into the ValueTask awaiter. |
|
@OsirisTerje I don't think there's a test we can write which will fail without this PR, since the C# pattern-based adapter catches this scenario anyways, so I had to use to the debugger to watch the flow. The tests I added passed even before this PR, but they hit the code paths (and the other tests hit the negative code paths which caused an exception previously). |
stevenaw
left a comment
There was a problem hiding this comment.
Thanks for noticing and fixing this @RenderMichael ! LGTM
|
I apologize for missing #4543. What is the reason that we don't use CSharpPatternBasedAwaitAdapter for ValueTask? |
Benchmark codeusing BenchmarkDotNet.Attributes;
using NUnit.Framework.Internal;
using NUnit.TestData;
namespace NUnit.Framework;
[MemoryDiagnoser(false)]
public class AwaitableBenchmarks
{
public IEnumerable<object> ValueData()
{
yield return Task.Yield();
yield return ValueTask.FromResult(30);
yield return ValueTask.FromException<object>(new Exception());
yield return new AwaitableReturnTypeFixture.CustomAwaitable();
yield return new AwaitableReturnTypeFixture.CustomAwaitableWithImplicitOnCompleted();
yield return new AwaitableReturnTypeFixture.CustomAwaitableWithImplicitUnsafeOnCompleted();
}
[Benchmark]
[ArgumentsSource(nameof(ValueData))]
public AwaitAdapter? After(object maybeTask)
{
var awaiter = ValueTaskAwaitAdapter.TryCreate(maybeTask)
?? CSharpPatternBasedAwaitAdapter.TryCreate(maybeTask)
?? FSharpAsyncAwaitAdapter.TryCreate(maybeTask);
if (awaiter is not null)
{
return awaiter;
}
throw new InvalidOperationException();
}
[Benchmark(Baseline = true)]
[ArgumentsSource(nameof(ValueData))]
public AwaitAdapter? Before(object maybeTask)
{
var awaiter = CSharpPatternBasedAwaitAdapter.TryCreate(maybeTask)
?? FSharpAsyncAwaitAdapter.TryCreate(maybeTask);
if (awaiter is not null)
{
return awaiter;
}
throw new InvalidOperationException();
}
}
I'm not versed enough in BenchmarkDotNet to give a better parameter name than the default I can't speak for why it was initially implemented, but it noticeably speeds up the ValueTask<> check (in exchange for higher allocations) without meaningfully regressing the other cases. |


Fixes #4639