One of the original goals of ValueTask
is handling functions that are "possibly async" in a zero-alloc fashion, for example:
```c#
public abstract ValueTask
public override ValueTask
{
// Possibly async
if (_cache.TryGetValue(service, out var result)) return new ValueTask
// Constructor is ambiguous for null, we have to repeatedly repeat ourselves.
if (_nullCache.Contains(service)) return new ValueTask
// Or:
if (_nullCache.Contains(service)) return new ValueTask
// Definitely async
return Impl(service);
async ValueTask
}
In Visual Studio this method is easy to type (as Intellisense supports `new` completion), but in VSCode and other editors the entirety of `ValueTask<Service>(service)` has to be typed out or copied from the method signature. The clarity and length of code also suffers, especially where any amount of nested generics are present (e.g. `Tuple` or `KeyValuePair`).
## Proposal
I propose adding the following to `ValueTask`, that follows from the static interface of `Task`:
```c#
public struct ValueTask
{
// Improves consistency with regards to Task.
// Prevents a Google search to determine if `return default` works (self-documenting).
public static ValueTask CompletedTask { get; }
public static ValueTask<T> FromResult<T>(T result);
// Could be replaced with FromResult(default(T)), but that doesn't seem as elegant.
public static ValueTask<T> FromDefault<T>();
}
This then allows generic parameter inference to work its magic, saving some keystrokes and greatly improving code clarity:
```c#
public override ValueTask
{
if (_cache.TryGetValue(service, out var result)) return ValueTask.FromResult(result);
if (_nullCache.Contains(service)) return ValueTask.FromDefault
/* ... */
}
EDITED: 2/25/2020 by @stephentoub to paste proposal from below here, and update it with the implementation one-liners to show the semantics.
```C#
public struct ValueTask
{
public static ValueTask CompletedTask => default;
public static ValueTask<TResult> FromResult<TResult>(TResult result) =>
new ValueTask<TResult>(result);
public static ValueTask FromCanceled(CancellationToken cancellationToken) =>
new ValueTask(Task.FromCanceled(cancellationToken));
public static ValueTask<TResult> FromCanceled<TResult>(CancellationToken cancellationToken) =>
new ValueTask<TResult>(Task.FromCanceled<TResult>(cancellationToken));
public static ValueTask FromException(Exception exception) =>
new ValueTask(Task.FromException(exception));
public static ValueTask<TResult> FromException<TResult>(Exception exception) =>
new ValueTask<TResult>(Task.FromException<TResult>(exception));
}
if (_nullCache.Contains(service)) return ValueTask.FromDefault<Service>();
Can currently be?
if (_nullCache.Contains(service)) return default;
All of these are already possible, e.g. using:
new ValueTask<T>(value)
instead of ValueTask.FromResult<T>(value)
default
or default(T)
instead of ValueTask.FromDefault<T>()
default
instead of ValueTask.CompletedTask
I think @jcdickinson's goal isn't to fix functionality holes but to gain type inference in some cases and clarity/symmetry in others?
I don't have a strong opinion on CompletedTask
and FromResult<T>
; I can see wanting the latter for type inference reasons, and that CompletedTask
helps avoid confusion about how the default ValueTask
actually is a completed task. If we did add FromResult
, that then opens up questions about FromException
, FromCanceled
, etc., as well, and how much parity is actually needed. Slippery slope :)
I'm not a big fan of FromDefault<T>()
... I don't think that adds a lot of value, doesn't have a counterpart, etc., and I don't think that should be added.
@stephentoub correct. I'll go ahead and remove FromDefault
. I did expect to see some friction with that.
FromException
can be done by simply throwing, but I can see how that refers back to my return default;
argument. I'll add those in.
c#
public struct ValueTask
{
public static ValueTask CompletedTask { get; }
public static ValueTask<T> FromResult<T>(T result);
public static ValueTask<TResult> FromCanceled<TResult>(CancellationToken cancellationToken);
public static ValueTask FromCanceled(CancellationToken cancellationToken);
public static ValueTask<TResult> FromException<TResult>(Exception exception);
public static ValueTask FromException(Exception exception);
}
Just my some cents: You can't use new ValueTask<T>(value)
when T is an anomyous type at all.
And missing type inference produces ugly code when T is a generic type like Dictionary<(int, int), Func<string, Task<long>>>
. People who use var
-- like me -- like type inference.
Just my some cents: You can't use new ValueTask
(value) when T is an anomyous type at all.
Can you share an example where you're using ValueTask<T>
with an anonymous type?
This might happen as soon as you are passing non-aync anonymous method delegates to (generic) methods like ValueTask<T> ExecuteAsync(Func<InputData, ValueTask<T>> code)
.
I btw just could prefix the async method with async
keyword but then the compiler would:
1) complain that there is no await
2) still create the async state machine enumerator -- which isn't needed. IMHO it should just compile my code using ValueTask ctor
or Task.FromResult
.
This might happen as soon as you are passing non-aync anonymous method delegates to (generic) methods like ValueTask
ExecuteAsync(Func > code).
Why doesn't new ValueTask<T>
work there? You literally have T
.
T is the anomyous type then:
```c#
public async ValueTask
{
var anonymousData = await ExecuteAsync(
input =>
{
var data = (from x in input groupby [..] select new {})
.SingleOrDefault(); // anonymous type
return new ValueTask??>(data);
});
// .. do some other (async) data retrieval
return new PublicType(/* data from anoymousdata and others */);
}
```
I see. That's a pretty complicated setup. It won't add complication then for you to add:
C#
internal static ValueTask<T> FromResult<T>(T result) => new ValueTask<T>(result);
somewhere in your program.
As I stated earlier, "I can see wanting the latter for type inference reasons", and I don't have a particular problem adding ValueTask.FromResult<T>(T result)
, it just hasn't popped as being particularly impactful.
True. Returning a value tuple would also be an option (but when EF is involved an intermediary (anonymous) class is still needed).
Having ValueTask
looking familar to Task
-- at least for FromResult
-- would be perfect.
BTW: The constructor call gets ambiguous when using null
as argument. I'm glad I figured out that return default;
just does the trick instead (looks far better than return new ValueTask<Dictionary<(int, int), Func<string, Task<long>>>>((Dictionary<(int, int), Func<string, Task<long>>>)null)
).
I'm here from a Google search on how to do ValueTask.CompletedTask. I'll note for future searchers that "return default" works. "default" means "a completed ValueTask".
There are other Task methods that would probably be very useful such as Task.WhenAll
for ValueTask
. I don't known of a way to do the equivalent with ValueTask
currently without converting to Task (AsTask()
).
WhenAll is technically possible (and is covered by https://github.com/dotnet/corefx/issues/24196), though my preference is that any combinators fall back to using Task. It's way too easy to get things wrong with ValueTask once you veer away from just directly awaiting one handed back to you by a method. Such a WhenAll would need to give you back the results of each one, as you wouldn't be able to touch any of the constituent ValueTask instances again.
I would like to see FromCanceled
and FromException
as well -- having the line if(foo) return new ValueTask<HttpResponseMessage>(Task.FromException<HttpResponseMessage>(...))
is super verbose.
I do not like FromDefault
, it doesn't seem to get us much over FromResult<T>(default)
or just default(ValueTask<T>)
.
I would also avoid WhenAll
due to reuse concerns.
I think this is important not just for type inference but for discoverability. The first thing I did the first time I used valuetask was try to use ValueTask.CompletedTask
, and ValueTask.FromResult
, before having to do an internet search to find that new
is the way to do it.
Approved as
```C#
public struct ValueTask
{
public static ValueTask CompletedTask => default;
public static ValueTask<TResult> FromResult<TResult>(TResult result) =>
new ValueTask<TResult>(result);
public static ValueTask FromCanceled(CancellationToken cancellationToken) =>
new ValueTask(Task.FromCanceled(cancellationToken));
public static ValueTask<TResult> FromCanceled<TResult>(CancellationToken cancellationToken) =>
new ValueTask<TResult>(Task.FromCanceled<TResult>(cancellationToken));
public static ValueTask FromException(Exception exception) =>
new ValueTask(Task.FromException(exception));
public static ValueTask<TResult> FromException<TResult>(Exception exception) =>
new ValueTask<TResult>(Task.FromException<TResult>(exception));
}
```
I assume that's not the implementation
public static ValueTask<TResult> FromResult<TResult>(TResult result) =>
new ValueTask<TResult>(Task.FromResult(result));
and its more like?
public static ValueTask<TResult> FromResult<TResult>(TResult result) =>
new ValueTask<TResult>(result);
Will watch the api review
I assume that's not the implementation
Right, it was just a stand-in for the semantics
Those are lovely API additions!
Quick question: will these new APIs be available in a future version of the System.Threading.Tasks.Extensions
NuGet package?
We're not currently in a position to produce a new version of that package with these.
Understood. Thanks for your reply!
Most helpful comment
I'm here from a Google search on how to do ValueTask.CompletedTask. I'll note for future searchers that "return default" works. "default" means "a completed ValueTask".