Asynchronous code makes applications responsive and scalable, but a few recurring mistakes can cause hangs, lost exceptions or worse performance than synchronous code.
Rule 1: async all the way
Blocking a Task with .Result or .Wait() wastes threads and, in some contexts, causes deadlocks. If a method calls asynchronous code, it must become asynchronous too.
// Avoid
var customer = repo.FindAsync(id).Result;
// Correct
var customer = await repo.FindAsync(id);
Rule 2: no async void
An async void method cannot be awaited and its exceptions cannot be caught with a normal try/catch. Use it only for event handlers; in every other case return Task.
Rule 3: pass the CancellationToken along
public async Task<Report> GenerateAsync(int year, CancellationToken ct)
{
var data = await _repo.LoadAsync(year, ct);
return await _engine.ProcessAsync(data, ct);
}
That way a web request cancelled by the user really stops the work, instead of continuing to consume resources.
When to use ValueTask
ValueTask avoids an allocation when the result is often already available, for example read from a cache:
public ValueTask<Product?> FindAsync(int id)
{
if (_cache.TryGetValue(id, out var p))
return ValueTask.FromResult<Product?>(p); // no allocation
return new ValueTask<Product?>(LoadFromDatabaseAsync(id));
}
But it comes with rules: a ValueTask is awaited only once and must not be stored for reuse. When in doubt, Task remains the safer choice; use ValueTask only where a measurement proves the benefit.
Comments (0)
No comments yet.