DEV Community

Damiaan
Damiaan

Posted on

Awaiting the Task.CompletedTask?

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 asyncmethod 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;        
}
Enter fullscreen mode Exit fullscreen mode

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();
}
Enter fullscreen mode Exit fullscreen mode

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();
        }
Enter fullscreen mode Exit fullscreen mode

Look at these two lines:

awaiter = Task.CompletedTask.GetAwaiter();
if (!awaiter.IsCompleted) { ... }
Enter fullscreen mode Exit fullscreen mode

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

  1. removing the async and
  2. returning a completed task directly without await:
public Task SomeMethod()
{
    // Some code here...

    return Task.CompletedTask;
}
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
jamey_h_77980273155d088d1 profile image
Jamie H

Nice posting! Looking forward to talking to you soon