IHostedService is the background worker you would have written

A long-running process alongside the web application — a queue consumer, a scheduler — is a first-class concept here rather than a separate supervisor configuration.

public class OrderConsumer : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            var message = await _queue.ReceiveAsync(stoppingToken);
            await _handler.Handle(message, stoppingToken);
        }
    }
}

// services.AddHostedService<OrderConsumer>();

The cancellation token threaded through every call is what makes shutdown graceful — a SIGTERM cancels it and the loop finishes the current message rather than being killed mid-write. That is the part a PHP worker has to build by hand with signal handlers, and getting it right is fiddly. Running the consumer in the same process as the web application is convenient and couples their scaling, which is a decision worth making deliberately rather than by default.