Reputation: 924
The following code does not compile
public ValueTask Foo()
{
return Task.Delay(1000);
}
but yields an Error CS0029: Cannot implicitly convert type 'System.Threading.Tasks.Task' to 'System.Threading.Tasks.ValueTask
as expected.
However, this
public async ValueTask Bar()
{
await Task.Delay(1000);
}
does compile fine.
I was just wondering how this works. Is this all down to compiler magic and its async-await syntactic sugar or is there something else going on?
For context: I came across this when implementing IAsyncDisposable.DisposeAsync()
.
Upvotes: 3
Views: 2699
Reputation: 396
Inside async method, you must return inner type of task, but you cannot return a task itself:
public async Task<int> Foo()
{
return 42; // that works
}
public async Task<int> Foo()
{
return Task.FromResult(42); // error
}
Look at the first example again: you don't need any task at all to return something from async method. async
keyword acts as some sort of wrap operator, that transforms any T
type to Task<T>
. And it can work with any task-like type.
On the other hand, await
operator acts like unwrap operator that transforms any Task<T>
type into inner type T
:
public async void Foo()
{
int x = await Task.FromResult(42); // that works
}
public async void Foo()
{
int x = Task.FromResult(42); // error
}
public async void Foo()
{
int x = await 42; // error too
}
And await
operator can work with any awaitable type, such as Task, ValueTask, ConfiguredTaskAwaitable, even YieldAwaitable
And this two operators can be combined in any way.
Let's look again in your code:
public async ValueTask Bar()
{
await Task.Delay(1000);
}
First, await
transforms Task
into void
. Next, async
transforms implicit void
into ValueTask
. There are nothing to wonder here.
Upvotes: 1
Reputation: 14856
Title doesn't match what's being asked. In fact, the question proves it's possible.
If you want to return ValueTask
that represents a Task
whithout having a method turned into a state machine, you can:
public ValueTask Foo()
{
return new ValueTask(Task.Delay(1000));
}
Upvotes: 2
Reputation: 273540
Is this all down to compiler magic and its async-await syntactic sugar?
In short, yes. Whenever you await
, the compiler needs to generate a state machine for that method. The task returned from the method then, is one that "represents" the state machine, rather than the single task that you are awaiting.
As a result, it doesn't matter what tasks you are awaiting anymore. The compiler just has to build the state machine according to where your await
s are in your method, and then build a new task.
Compare the code generated from the following snippets on SharpLab:
1:
async Task Bar()
{
await Task.Delay(1000);
}
2:
async ValueTask Bar()
{
await Task.Delay(1000);
}
The only substantial difference is that one uses AsyncTaskMethodBuilder
to build the task being returned, and the other using AsyncValueTaskMethodBuilder
.
For more details about the difference of awaiting a task vs directly returning the task, see this chain of duplicates.
Upvotes: 4