I'm looking at Task.Delay(int)
decompiled in ILSpy:
// System.Threading.Tasks.Task
[__DynamicallyInvokable]
public static Task Delay(int millisecondsDelay)
{
return Task.Delay(millisecondsDelay, default(CancellationToken));
}
This method is used like await Task.Delay(5000);
, and the intellisense even says "(awaitable)":
So how is it that Task.Delay(int)
isn't marked async
(public static async Task Delay(int millisecondsDelay)
)?
The call to the async method starts an asynchronous task. However, because no Await operator is applied, the program continues without waiting for the task to complete. In most cases, that behavior isn't expected.
If we don't await any of the tasks, the result will be similar to the previous example with a single task: the exception will be lost with the tasks. However, if we await all the tasks, considering multiple exceptions can be thrown, one for each task, the result for the parent method may not be what we are expecting.
async, await, and TaskThe await keyword waits for the async method until it returns a value. So the main application thread stops there until it receives a return value. The Task class represents an asynchronous operation and Task<TResult> generic class represents an operation that can return a value.
NET, Task. Run is used to asynchronously execute CPU-bound code.
What's awaitable is the Task
Task.Delay
returns. Each method returning a Task
/Task<TResult>
is awaitable. async
is just an implementation detail allowing you to use await
in that method and the whole state machine it generates.
More generally, every thing that has a GetAwaiter
method (extension methods count as well) that return something that has IsCompleted
, OnCompleted
and GetResult
can be awaited.
For example, Task.Yield
returns YieldAwaitable
which isn't a Task
and looks like this:
public struct YieldAwaiter : ICriticalNotifyCompletion, INotifyCompletion
{
public void OnCompleted(Action continuation);
public void UnsafeOnCompleted(Action continuation);
public void GetResult();
public bool IsCompleted { get; }
}
*UnsafeOnCompleted
here is just an optimization, await
would work without it.
It's important to note that the compiler in this case (same as in other cases like GetEnumerator
for foreach
) doesn't expect an interface or a base class. It basically uses duck typing (i.e. "if it walks like a duck...") and simply looks for a GetAwaiter
method that returns anything (doesn't matter what type or interface or if it's a class or a struct) that has the other 3 members (IsCompleted
, OnCompleted
and GetResult
)
For example, this is how you can make await "bar"
compile (it will fail in runtime of course):
public static Awaiter GetAwaiter(this string s)
{
throw new NotImplementedException();
}
public abstract class Awaiter : INotifyCompletion
{
public abstract bool IsCompleted { get; }
public abstract void GetResult();
public abstract void OnCompleted(Action continuation);
}
In conclusion, you don't need async
to return an awaitable and moreover most Task
-returning methods in the .Net framework don't use it and explicitly return a Task
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With