- If a method A calls another method B that returns a Task (or Task<T>) then the calling method does not block on the completion of that task, unless it either:
- awaits that task, or
- waits on the result of that task
- The calling method cannot await a task unless it is declared with the async modifier, which causes the compiler to build a state machine (similar to the IEnumerable
iterator) for that awaitable method.
- In point 1 above, the behaviour is independent of whether or not B is marked with the async keyword
- It is this non-blocking behaviour of A that allows the calling thread to proceed past the point where B is invoked. It's even possible for the thread's call stack to unwind, and for the thread to go back to reading the Windows message queue if it was the UI thread.
- The compiler will only warn you "because this call is not awaited ..." if B was marked with async.
- It is expected that A will do something with the Task returned by B; at the very least there should be some code to check that the Task did not throw any exceptions. If - instead of B - we have an async method C that returns void then we do not present A with any opportunity to monitor the completion of C. Unobserved exceptions thrown during the execution of C could indicate corrupted program state and can be configured to terminate the application in much the same way that an unhandled exception in synchronous code can unwind a stack fully and terminate the process.