Do I always have to await on an Async method of a disposable object instead of returning its Task?
Interview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.
Introduction
When an async method uses a disposable resource (inside using), you must await the async operation before the using block exits. If you return the Task without awaiting, the using block disposes the resource immediately, while the async operation is still running — causing ObjectDisposedException or corrupted data. The rule is simple: if a using statement wraps the async call, you must await inside that scope. If there is no using (the caller manages the resource lifetime), returning the Task directly is safe and actually preferred for performance.
The Problem: Return Without Await
The using statement calls Dispose() when execution leaves its scope. Since return stream.ReadToEndAsync() returns immediately (the Task is not yet complete), the using block disposes the StreamReader while the read is still in progress.
The Fix: Await Inside Using
The await suspends the method until ReadToEndAsync completes. Only then does the using block dispose the stream. The async state machine generated by the compiler ensures Dispose happens after the awaited operation finishes.
When Returning Task Directly Is Safe
If the method does not own the disposable resource (no using), returning the Task directly is safe and avoids the overhead of the async state machine:
Performance Benefit of Returning Task
Version 2 is slightly more efficient because it avoids the compiler-generated async state machine. However, the difference is negligible for most applications.
IAsyncDisposable Pattern (C# 8.0+)
For resources with async cleanup, use await using:
Implementing IAsyncDisposable
Decision Guide
| Scenario | Return Task? | Use async/await? |
using wraps async call | No | Yes, must await |
No using, no try/catch | Yes | No, return Task directly |
| try/catch around async | No | Yes, must await |
| Multiple sequential awaits | No | Yes, must await each |
| Simple pass-through | Yes | No, return Task directly |
Common Pitfalls
- Returning a Task from inside a
usingblock: This is the most common mistake. Theusingdisposes the resource as soon as the method returns, while the Task is still running. Alwaysawaitinsideusingblocks. - Wrapping a single awaitable in async/await unnecessarily: If there is no
using, no try/catch, and only one async call, returning theTaskdirectly is more efficient. Addingasync/awaitcreates an unnecessary state machine. - Forgetting
await usingforIAsyncDisposable: Usingusing(synchronous) on anIAsyncDisposablecallsDispose()instead ofDisposeAsync(), which may block or skip async cleanup. Useawait usingfor types that implementIAsyncDisposable. - Assuming
Disposewaits for async operations:Dispose()is synchronous and does not wait for pending async operations. If you callstream.WriteAsync()and thenDispose()immediately, the write may not complete. Alwaysawaitthe operation before disposal. - Not handling exceptions in returned Tasks: When returning a
Taskdirectly (without async/await), exceptions thrown synchronously before the Task starts are thrown at the call site, not when the Task is awaited. This can cause confusing behavior. Use async/await when exception consistency matters.
Summary
- If your method has a
usingorawait usingblock around an async call, you mustawaitinside that scope - If your method does not own the resource (no
using) and has no try/catch, returning theTaskdirectly is safe and slightly more efficient - Use
await usingforIAsyncDisposabletypes to ensure asynchronous cleanup - The key rule: disposal must happen after the async operation completes, and
awaitis what guarantees that ordering - When in doubt, use
async/await— the state machine overhead is negligible compared to the risk of disposing a resource prematurely
Related reading
- Do I have to acquire lock before calling condition_variable.notify_one?
- Do I have to use Pin when returning a boxed trait object from an async method?
- Do I need to be concerned with race conditions with asynchronous Javascript?
- Do I need to kill a thread written like this? Or will it automatically end?
- Do I need to reset a streamC back to the start?
- Do the new C 5.0 'async' and 'await' keywords use multiple cores?
- Do I need to synchronize a call to the interrupt method?
- Do Kubernetes liveness probes run in parallel with your application?

OOD Fundamentals
Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
Interview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.