In this lesson, you will learn how to distinguish between ad-hoc task parallelism and long-running background services to ensure your system architecture remains scalable and stable.
The Problem of Scope
When building high-performance systems, you often need to perform work outside the main request-response flow. Developers frequently fall into the trap of spawning "fire-and-forget" tasks for everything. While Task.Run is a powerful tool for CPU-bound parallelism within a request, it is dangerous for long-running operations.
If you trigger a background operation using Task.Run inside a controller, that task is tethered to the lifecycle of the web request. If the app pool recycles or the host shuts down, that task is abruptly terminated, leading to data corruption or partial state updates.
When to use Task Parallelism
Use Task.Run or Parallel.ForEach only when you need to utilize multiple cores to complete a finite, short-lived unit of work that contributes to the current user request.
// Use Task Parallelism for CPU-intensive calculations within a request
public async Task<int> CalculateBatchAsync(List<Data> items)
{
// This is scoped to the request lifecycle
return await Task.Run(() => items.AsParallel().Sum(i => i.Value));
}
When to use Background Services
Conversely, BackgroundService (implementing IHostedService) is intended for tasks that outlive the request lifecycle. These services are managed by the host itself, allowing for controlled startup and, crucially, graceful shutdown.
If you have a task that needs to run for minutes, hours, or continuously (like a queue processor or a heartbeat monitor), you must host it as a background service. This ensures that the application runtime is aware of the task, allowing it to complete current work before the process exits.
public class QueueProcessor : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
// Process queue items independently of any specific HTTP request
await DoWorkAsync(stoppingToken);
}
}
}
By choosing the right tool, you prevent "orphaned" tasks and ensure your system handles load peaks without sacrificing data integrity.
Key Takeaway: Use Task Parallelism for short-lived, request-bound operations, and use Background Services for long-running, process-level tasks that require lifecycle management and graceful shutdown support.