After I saw a colleague struggling with Task.CompletedTask, I thought I might be worth sharing our learning.
What is the Task.CompletedTask?
The Task.CompletedTask is a property on the Task class that returns a task that has already completed successfully, as the Status property will contain RanToCompletion.
In a sense, Task.CompletedTask is conceptually similar to Task.FromResult but for void returns. Both are used to create tasks that are done executing. One returns a value, the other doesn't.
When to use Task.Completed
It's useful when you have a Task return type in a method and you need to return without actually doing any asynchronous work.
Consider using the 'await' operator
My best guess is you have warning CS1998
Let's see what it says:
This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.
This is where programmers think they need to follow the advice from the warning. And they are trying to add more stuff to the solution trying to resolve the issue. When they come up with await Task.CompletedTask;
the warning will disappear.
Is adding await Task.CompletedTask the right solution?
Let's take a step back.
Having an async
method will add a state machine to your method. This will involve a slight (unnoticably) overhead.
Here is a c# method
public async Task M()
{
return;
}
When compilation is done, there will be a new method in your class trying to handle any await.
private void MoveNext()
{
int num = <>1__state;
try
{
}
catch (Exception exception)
{
<>1__state = -2;
<>t__builder.SetException(exception);
return;
}
<>1__state = -2;
<>t__builder.SetResult();
}
Replacing the return with await Task.CompletedTask
converts the MoveNext() generated method to a beast.
private void MoveNext()
{
int num = <>1__state;
try
{
TaskAwaiter awaiter;
if (num != 0)
{
awaiter = Task.CompletedTask.GetAwaiter();
if (!awaiter.IsCompleted)
{
num = (<>1__state = 0);
<>u__1 = awaiter;
<M>d__0 stateMachine = this;
<>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
return;
}
}
else
{
awaiter = <>u__1;
<>u__1 = default(TaskAwaiter);
num = (<>1__state = -1);
}
awaiter.GetResult();
}
catch (Exception exception)
{
<>1__state = -2;
<>t__builder.SetException(exception);
return;
}
<>1__state = -2;
<>t__builder.SetResult();
}
Look at these two lines:
awaiter = Task.CompletedTask.GetAwaiter();
if (!awaiter.IsCompleted) { ... }
We already learned that the Task.CompletedTask is a property that is already completed. So the compiler will be checking for completion of something that is already completed.
So adding in the await Task.Completed task is adding unnecessary asynchronous overhead.
Although the performance hit might be minimal, it's still an unnecessary context switch and state machine generation, which could be avoided.
On top of that it can be misleading to other developers reading your code. Seeing await typically suggests there is an asynchronous operation occurring, but in this case, it does not.
How to resolve warning CS1998
If there's no actual asynchronous work, consider
- removing the async and
- returning a completed task directly without await:
public Task SomeMethod()
{
// Some code here...
return Task.CompletedTask;
}
This avoids the overhead associated with async and await and is more efficient and clear.
While await Task.CompletedTask; is not inherently "bad", it's unnecessary and can be avoided. It's typically better to return Task.CompletedTask directly when no asynchronous operation is required.
Top comments (1)
Nice posting! Looking forward to talking to you soon