Awaiting an incomplete operation can suspend the method without blocking the current thread. It does not make CPU work parallel or cheaper.

For I/O, await the asynchronous API directly:

public async Task<Customer> LoadCustomerAsync(
    Guid customerId,
    CancellationToken cancellationToken)
{
    Customer? customer = await db.Customers.SingleOrDefaultAsync(
        c => c.Id == customerId,
        cancellationToken);

    return customer
        ?? throw new KeyNotFoundException("Customer was not found.");
}

While the database operation is in flight, the calling thread does not need to stay blocked.

CPU-bound work is different:

ImageData ResizeImage(
    ImageData source,
    CancellationToken cancellationToken)
{
    cancellationToken.ThrowIfCancellationRequested();

    // Perform CPU-bound resizing.
    return ResizeCore(source, cancellationToken);
}

The resize operation remains synchronous because it is CPU-bound. A desktop UI may explicitly offload it to keep the UI thread responsive:

ImageData resized = await Task.Run(
    () => ResizeImage(source, cancellationToken),
    cancellationToken);

Task.Run moves the computation to a ThreadPool work item. It can keep a UI thread responsive, but it does not make a single-threaded resize algorithm faster or parallel.

The token passed to Task.Run can prevent the delegate from starting. After it starts, ResizeImage must check cancellationToken cooperatively at useful intervals. This is worthwhile only when the work runs long enough for cancellation to matter. Task.Run does not forcibly interrupt the computation.

In ASP.NET Core, wrapping CPU work in Task.Run usually queues it as another ThreadPool work item, adding scheduling overhead without improving throughput. If the operation is expensive, decide whether it belongs in the request, a bounded parallel algorithm, or a background job. Moving it to a background worker changes ownership and request latency. It adds compute capacity only when the worker has separate resources.

CPU parallelism uses multiple execution resources concurrently to reduce the completion time of suitable work. Parallel.For, Parallel.ForEach, PLINQ, and specialized multithreaded algorithms can distribute computation across multiple cores. SIMD provides a different form of data-level parallelism by processing multiple values with one instruction.

Multithreaded parallelism can increase contention and reduce system throughput when every request competes for the same cores. Measure the workload and bound the degree of parallelism. TPL parallelization can become slower when its partitioning and scheduling overhead exceeds the saved computation.

Benchmark SIMD separately because its benefit depends on vectorization, data layout, memory access, and hardware support.

Keep the concepts separate:

  • asynchronous I/O avoids blocking while an external operation progresses
  • concurrency allows operation lifetimes to overlap
  • CPU parallelism executes computation simultaneously across multiple cores

An Async suffix does not prove that a method is non-blocking. Expensive synchronous computation before the first incomplete await, or inside a later continuation, still consumes a thread and CPU time.

Use async all the way through real I/O boundaries. For CPU work, choose responsiveness, parallelism, or background execution deliberately.