Async Queues In Laravel
Stefan Bogdanescu
Founder & Senior Architect · 2026-06-29
Async Queues in Laravel: Why Your Jobs Aren't Truly Asynchronous
As a senior developer working with modern PHP frameworks, implementing asynchronous processing using queues is a fundamental skill. Many developers encounter this exact scenario: they set up the queue configuration, dispatch jobs correctly, and yet the perceived result seems synchronous—the web request waits for the background task to finish before returning a response.
This post will diagnose why your setup might be behaving synchronously and guide you toward implementing true, scalable asynchronous job processing in Laravel.
The Misconception: Dispatch vs. Execution
The core misunderstanding often lies in confusing dispatching a job with executing that job asynchronously. When you dispatch a job using methods like dispatch() or dispatchSync(), the work is handed off to the queue system, but the immediate execution flow of your controller remains tied to the dispatcher.
Your provided example demonstrates this perfectly:
// In SomeController.php
public function store(){
Log::debug('before dispatch');
$this->dispatch(new SendGiftCommand($model1)); // Job is placed in the queue
Log::debug('before dispatch');
return true; // The controller returns immediately
}
When using a standard driver like database, Laravel simply writes a record into the database table (the jobs table) indicating that a job needs to be run. This action is extremely fast, which is why your controller returns immediately. The asynchronous part—the actual execution of SendGiftCommand::handle()—is handled by a separate process, not by the web request thread itself.
The fact that you see the logs in sequence (before dispatch -> handle -> end) suggests that while the job was dispatched, the system might be waiting for a short-lived listener or an immediate execution context if you are using specific command listening methods, which is not the intended background pattern.
Achieving True Asynchronicity with Queue Workers
To achieve true asynchronicity, you must separate the producer (the web request that dispatches the job) from the consumer (the process that executes the job).
The commands you used, like php artisan queue:listen, are useful for debugging and testing environments because they run a persistent loop on the command line, actively polling the queue. However, in a production environment, relying solely on this listener is insufficient because it ties up your terminal session and doesn't scale well across multiple servers.
The Solution: Dedicated Queue Workers
True asynchronous processing relies on running dedicated queue workers as separate, long-running processes that continuously pull jobs from the queue and execute them independently of your web application requests.
You need an external process manager to ensure these workers run reliably in production environments. Tools like Supervisor are industry standards for managing long-running background processes.
Here is the typical asynchronous workflow:
- Producer (Web Request): Your controller dispatches the job (
dispatch(new SendGiftCommand(...))). This is fast and returns control to the user immediately. - Queue: The job record is persisted (e.g., in your
jobstable). - Consumer (Worker): A separate command, run continuously on a server, polls the queue for new jobs and executes them one by one.
Implementation Steps
To set up robust asynchronous processing, follow these steps:
1. Ensure Proper Configuration:
Verify your config/queue.php is correctly configured for your chosen driver (database, Redis, or Beanstalkd). For high-throughput applications, drivers like Redis are often preferred over the database driver for better performance. As you build robust systems, understanding these underlying mechanics is crucial, much like grasping the architecture principles promoted by resources such as Laravel Company.
2. Running the Worker Process:
Instead of using queue:listen in production, you start a dedicated worker process. For example, if you are using the default database driver, you would run:
php artisan queue:work
or, for better control and supervision, use Supervisor to manage this process perpetually. This worker will sit independently, waiting for jobs to appear in the queue, completely decoupled from the web server handling your HTTP requests.
3. Handling Time-Consuming Tasks:
In your job itself (SendGiftCommand), if you need the actual work to happen outside the request context, ensure that the logic inside the handle() method is purely operational. If the task involves heavy I/O or long waits (like your example's sleep(4)), placing it into a queue system ensures that these waits occur in the worker process, not blocking the web server thread.
Conclusion
The key takeaway is this: Queueing provides decoupling, not inherent asynchronicity. The job dispatch is fast, but the execution must be delegated to an external, persistent worker process. By implementing dedicated queue workers managed by a supervisor system, you move beyond simple listening and establish a scalable, robust architecture capable of handling heavy background tasks efficiently.