Just for the sanity of other readers, the usual purpose of Task.FromResult<T>, Task.CompletedTask, Task.FromCancelation and Task.FromException() is to provide simple factory methods for various types of Task (i.e. with / without return payload, or to return an exception or mimic cancellation), and in all cases, the tasks returned will be regarded as IsCompleted, as per the source:
private const int TASK_STATE_COMPLETED_MASK = TASK_STATE_CANCELED | TASK_STATE_FAULTED
| TASK_STATE_RAN_TO_COMPLETION;
As per @Marc's answer, awaiting an already IsCompleted Task short circuits the awaiter and execution will continue synchronously on the same thread.
As per my comment, it would be highly unusual to directly await a Task created by Task.CompletedTask or Task.FromResult, as this compiler would generate an unnecessary async state machine wrapper, which is total overkill in the OP's scenario.
A common scenario for using the various completed Task factory methods would be in mocking during Unit testing, where the class / interface being mocked requires a Task to be returned but otherwise has no need for async. For example, if the following production interface needed mocking or stubbing:
public interface ISomeInterface
{
Task<DateTime> GetDateAsync();
}
which could be stubbed as follows:
public class MyMock : ISomeInterface
{
public Task<DateTime> GetDateAsync() // no async
{
// Directly return a completed task return a fake result
return Task.FromResult(new DateTime(2019, 11, 12, 0, 0, 0, DateTimeKind.Utc));
}
}
The production class being tested (SUT) would then likely await the result of GetDateAsync() on the injected (and now mocked) ISomeInterface, and will usually be none the wiser that the called method just rubber stamped the Task and returned fake data synchronously.