Making an asynchronous task in Flask
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
In Flask, a truly asynchronous background task usually means moving work out of the request-response cycle and into a worker process. For simple demos you can start a thread, but for production-style jobs such as sending email, generating reports, or calling slow external services, a task queue such as Celery is the more reliable pattern.
Why a Background Worker Is Better Than Blocking the Request
If a Flask route performs a slow job directly, the web worker is tied up until the job finishes. That hurts throughput and makes the client wait longer than necessary.
A better flow is:
- receive the request
- enqueue the work
- return a task id immediately
- let a worker process the job in the background
- provide a status endpoint if the client needs progress or results
This design keeps the HTTP layer responsive and separates web concerns from job execution.
Minimal Flask and Celery Setup
A common starter stack is Flask plus Celery with Redis as both broker and result backend.
Create an app.py file like this:
This example does three important things:
- defines a background task
- enqueues it with
.delay() - exposes a status endpoint using the task id
Run the Web App and the Worker Separately
Celery works because the worker process is separate from Flask. You need both processes running.
Start Redis first, then run:
In another terminal, start the worker:
Now send a request:
The response should contain a task id. Query the status endpoint with that id until the task reaches SUCCESS.
Know When Simpler Approaches Are Acceptable
For very small local tools, some developers use a background thread:
This can be fine for lightweight, non-critical tasks in a single-process development setup. It is not a substitute for a proper queue when you need retries, persistence, monitoring, or multiple web workers. If the process restarts, the thread and its work are simply gone.
Design the API Around Asynchronous Behavior
Once work moves to the background, the HTTP contract changes. A task endpoint should normally return 202 Accepted instead of pretending the work is already done. Clients should then poll a status route or receive a callback from some other system.
That design choice matters because it forces you to separate "request accepted" from "job completed." The separation is what makes asynchronous processing operationally sound.
Common Pitfalls
- Starting Celery configuration in Flask but forgetting to run a worker leaves tasks queued forever with no execution.
- Doing long work directly inside the route blocks the web worker and defeats the point of background processing.
- Using threads for important production jobs is risky because in-memory work can disappear on process restart.
- Returning
200 OKwith no task tracking makes clients think the work is finished when it has only been scheduled. - Ignoring a result backend or status mechanism makes debugging difficult because you cannot inspect task state or failures.
Summary
- In Flask, production-style asynchronous tasks are usually handled by a worker queue, not by the request thread.
- Celery plus Redis is a common and practical starting point.
- Enqueue work with
.delay()and return a task id immediately. - Run the Flask app and Celery worker as separate processes.
- Use threads only for small, disposable background jobs where reliability is not critical.

