Prefect tasks are atomic pieces of work that you might want to cache or retry. Tasks have .submit() and .map() methods to simplify concurrent execution of a task in a given workflow.
If you need your task results available to the calling context, you probably want to use Prefect’s task runners via .submit() or .map() instead.
Sometimes your parent workflow doesn’t need to resolve a task’s result, it just needs the task to produce some side effect or save a result. In this case, the caller doesn’t need to waste time blocking on the task’s completion - instead, it can “background” that task run somewhere else.
Background tasks are built for this use case. They allow you to defer a task’s execution to a task worker running in a separate process.
Prefect background tasks can be used in place of tools like Celery and RabbitMQ.
Background tasks are useful for dispatching heavy and/or blocking work from your application or workflow to task workers on static infrastructure that you can scale or manage independently.
For example, imagine a web application that needs to trigger an agentic while loop for each request, which we encapsulate as a @task-decorated function named run_agent_loop. The task will likely run longer than an acceptable request-response cycle. You can delay() the expensive task from your endpoint to any task workers subscribed to run_agent_loop’s runs.
Use the .delay() method to background a run of this task
Use the .serve() method or serve() function to start a task worker and execute any waiting task runs
Copy
add.delay(1, 2) # background one task runadd.delay(42, 100) # background another task runadd.serve() # start a task worker and execute any waiting task runs
The .serve() method starts a task worker subscribed to that specific task’s runs.
Copy
from prefect import task@task(log_prints=True)def add(a: int, b: int): print(f"{a} + {b} = {a + b}")add.delay(1, 2) # background one task runadd.delay(42, 100) # background another task runadd.serve() # start a task worker and execute any waiting task runs
Copy
22:56:01.765 | INFO | prefect.tasks - Created task run 'add'.22:56:02.010 | INFO | prefect.tasks - Created task run 'add'.22:56:02.167 | INFO | prefect.task_worker - Starting task worker...22:56:02.167 | INFO | prefect.task_worker - Subscribing to runs of task(s): add22:56:02.428 | INFO | prefect.task_worker - Received task run: 71d4716c-5a28-4518-8376-bfd810a88093 - add22:56:02.435 | INFO | prefect.task_worker - Submitting task run 'add' to engine.22:56:02.476 | INFO | prefect.task_worker - Received task run: 225f054d-4fe7-4ed9-9ba1-9ba995b74d0e - add22:56:02.481 | INFO | prefect.task_worker - Submitting task run 'add' to engine.22:57:34.673 | INFO | Task run 'add' - 1 + 2 = 322:57:34.681 | INFO | Task run 'add' - Finished in state Completed()22:57:34.697 | INFO | Task run 'add' - 42 + 100 = 14222:57:34.700 | INFO | Task run 'add' - Finished in state Completed()
.delay() has the same signature as the @task decorated function.
Subscribe to many background tasks at once by providing the serve() utility more than one task:
22:27:09.590 | INFO | prefect.tasks - Created task run 'add'.22:27:09.868 | INFO | prefect.tasks - Created task run 'add'.22:27:10.114 | INFO | prefect.tasks - Created task run 'add'.22:27:10.384 | INFO | prefect.tasks - Created task run 'multiply'. 22:27:10.611 | INFO | prefect.tasks - Created task run 'multiply'. 22:27:10.855 | INFO | prefect.tasks - Created task run 'multiply'.22:27:10.870 | INFO | prefect.task_worker - Starting task worker...22:27:10.871 | INFO | prefect.task_worker - Subscribing to runs of task(s): add | multiply22:27:11.117 | INFO | prefect.task_worker - Received task run: 0f955090-89ce-402a-a7bd-057c5cecb0ae - add22:27:11.124 | INFO | prefect.task_worker - Submitting task run 'add' to engine.22:27:11.197 | INFO | Task run 'add' - 1 + 4 = 5 22:27:11.203 | INFO | Task run 'add' - Finished in state Completed()22:27:11.681 | INFO | prefect.task_worker - Received task run: 8969b4d1-b662-452e-95e3-7536cb9f6a0d - multiply22:27:11.688 | INFO | prefect.task_worker - Submitting task run 'multiply' to engine.22:27:11.724 | INFO | prefect.task_worker - Received task run: 0bf25ec4-cdae-44bd-a2b6-1fa9cdd2ba4e - add22:27:11.727 | INFO | prefect.task_worker - Submitting task run 'add' to engine.22:27:11.784 | INFO | prefect.task_worker - Received task run: 0502dd78-6afd-4d75-8ce4-eb45a2d004a9 - multiply22:27:11.789 | INFO | prefect.task_worker - Submitting task run 'multiply' to engine.22:27:11.792 | INFO | Task run 'multiply' - 1 * 4 = 422:27:11.802 | INFO | Task run 'multiply' - Finished in state Completed()22:27:11.831 | INFO | prefect.task_worker - Received task run: 53b866c4-0bb3-4941-af2e-ce95f7302595 - multiply22:27:11.836 | INFO | Task run 'add' - 2 + 5 = 722:27:11.838 | INFO | prefect.task_worker - Submitting task run 'multiply' to engine.22:27:11.847 | INFO | Task run 'add' - Finished in state Completed()22:27:11.880 | INFO | prefect.task_worker - Received task run: 6afc2483-6a6e-40cf-895a-b82a37bddd5a - add22:27:11.885 | INFO | prefect.task_worker - Submitting task run 'add' to engine.22:27:11.916 | INFO | Task run 'multiply' - 2 * 5 = 1022:27:11.926 | INFO | Task run 'multiply' - Finished in state Completed()22:27:11.963 | INFO | Task run 'multiply' - 3 * 6 = 1822:27:11.970 | INFO | Task run 'multiply' - Finished in state Completed()22:27:11.989 | INFO | Task run 'add' - 3 + 6 = 922:27:11.993 | INFO | Task run 'add' - Finished in state Completed()
.map() accepts Iterable[P.args], Iterable[P.kwargs] or unmapped inputs as well as the deferred: bool argument to control whether the tasks are run in the background (instead of the current context’s task runner)
Task workers are push-based consumers that subscribe to some set of tasks’ runs. They can subscribe to many tasks, and be safely scaled horizontally (e.g. replicas: 4).
They generally do not need to be interacted with by Prefect users. Instead, they are started and stopped implicitly when you call .serve() or serve().
Examples are generally Docker Compose setups that can be run locally with docker compose up. However, as shown above, you can decouple the task submission and execution however you like.
Otherwise, start a Prefect server by running one of the following commands:
Copy
# blocks the current terminal sessionprefect server start# run in a detached containerdocker run -d -p 4200:4200 --name prefect-server prefecthq/prefect:3-latest prefect server start --host 0.0.0.0
In minimal-local-setup you’ll find a minimal fastapi application using background tasks.
Copy
cd minimal-local-setup
Copy
├── README.md├── api.py├── requirements.txt├── tasks.py└── test
There’s a test script that starts an ephemeral web server and task worker, then sends a demo request and cleans up.
Copy
cat test
If you’re comfortable, permit and run the script.
Copy
chmod +x test./test
Copy
» ./test[+] Starting API server (background)... Logging to /tmp/api.log[+] Starting task worker (background)... Logging to /tmp/tasks.log[+] Submitting job via curl... {"message":"submitted task run UUID('3d10165f-a15d-4440-abdb-75872ced6407')"}[*] Letting processes run for 3 seconds...[!] Times up! Stopping processes...[>] Final API server log contents (/tmp/api.log): 23:58:22.224 | INFO | prefect.tasks - Created task run 'some_work'. View it in the UI at 'http://127.0.0.1:4200/runs/task-run/3d10165f-a15d-4440-abdb-75872ced6407'[>] Final task worker log contents (/tmp/tasks.log): 23:58:21.152 | INFO | prefect.task_worker - Starting task worker... 23:58:21.153 | INFO | prefect.task_worker - Subscribing to runs of task(s): some_work 23:58:22.278 | INFO | prefect.task_worker - Received task run: 3d10165f-a15d-4440-abdb-75872ced6407 - some_work 23:58:22.281 | INFO | prefect.task_worker - Submitting task run 'some_work' to engine. View in the UI: http://127.0.0.1:4200/runs/task-run/3d10165f-a15d-4440-abdb-75872ced6407 23:58:22.314 | WARNING | prefect.client - Your Prefect server is running an older version of Prefect than your client which may result in unexpected behavior. Please upgrade your Prefect server from version 3.2.12 to version 3.3.4.dev1+9.g1cfffaaff or higher. 23:58:22.318 | INFO | Task run 'some_work' - doing some work with some_input='dude i found the best cat meme' 23:58:22.323 | INFO | Task run 'some_work' - Finished in state Completed()[*] Cleaning up background processes...[*] Cleanup complete.
Otherwise feel free to run the commands at your own pace in separate terminals.
Copy
# start the web serveruv run --with-requirements requirements.txt uvicorn api:app --reload
Copy
# start the task workeruv run --with-requirements requirements.txt tasks.py
If you’re already running a Prefect server, kill it for the following steps.
For example, to kill the prefect-server container started in Step 2, run:
In minimal-docker-compose you’ll find a minimal fastapi application defined in main.py with a /POST /job endpoint that calls process_job.delay(**job_request).