Skip to main content

The Result API Reference

The Result API in Celery provides a unified interface for tracking task execution, retrieving return values, and inspecting metadata. It acts as the bridge between the asynchronous execution environment (the workers) and the caller process.

The core of this API is implemented in celery/result.py, centered around the AsyncResult class and its specialized variants.

Core Result Classes

The Result API is built on a hierarchy that allows for both remote (backend-backed) and local (eager) result handling.

ResultBase

ResultBase is the abstract foundation for all result types. Its primary role is to maintain the parent attribute, which is essential for tracking task chains and lineages.

AsyncResult

AsyncResult is the primary class used by developers to interact with tasks. It represents a task that may or may not have finished executing. When you call .delay() or .apply_async() on a task, an AsyncResult instance is returned.

You can also instantiate it manually if you have the task UUID:

from celery.result import AsyncResult
from proj.celery import app

result = AsyncResult('task-uuid-123', app=app)

EagerResult

EagerResult is used when tasks are executed locally (e.g., when task_always_eager is set to True). It mimics the AsyncResult API but stores the result in memory. Unlike AsyncResult, EagerResult.ready() always returns True, and its get() method returns the result immediately without contacting a backend.

Retrieving Results and State

The AsyncResult class provides several properties and methods to inspect the status of a task.

State Properties

  • state (or status): Returns the current state of the task (e.g., PENDING, STARTED, SUCCESS, FAILURE).
  • ready(): Returns True if the task has finished executing (successfully or otherwise).
  • successful(): Returns True if the task finished with a SUCCESS state.
  • failed(): Returns True if the task finished with a FAILURE state.

Metadata Retrieval

Beyond the return value, AsyncResult can retrieve extended metadata if the backend supports it and result_extended is enabled in the configuration.

# Basic metadata
print(result.result) # The return value or exception instance
print(result.traceback) # The stack trace if the task failed

# Extended metadata (requires result_extended=True)
print(result.args) # Positional arguments passed to the task
print(result.kwargs) # Keyword arguments passed to the task
print(result.worker) # The name of the worker that executed the task
print(result.date_done) # UTC timestamp of completion

The exists() Method

Introduced in version 5.7.0, exists() allows you to distinguish between a task that is truly PENDING (waiting in the queue) and a task ID that has no record in the backend (e.g., expired or never existed).

if result.exists():
print(f"Task {result.id} is known to the backend.")

Synchronous Retrieval and Deadlocks

The get() method is used to wait for a task to complete and retrieve its result.

try:
value = result.get(timeout=10, propagate=True)
except TimeoutError:
print("The task took too long!")

Critical Warning: Deadlocks

The implementation of get() includes a safety check via assert_will_not_block(). Calling result.get() inside a task is strongly discouraged because it can lead to resource exhaustion and deadlocks where workers are waiting for results from other tasks that cannot start because all workers are busy waiting.

By default, disable_sync_subtasks=True is passed to get(), which triggers a RuntimeError if called within a task context. If you must bypass this, you can use the allow_join_result context manager:

from celery.result import allow_join_result

with allow_join_result():
result.get()

Result Hierarchies and Traversal

Tasks in Celery often form complex structures like chains, groups, or chords. AsyncResult provides tools to navigate these relationships.

Parent and Children

  • parent: Points to the AsyncResult of the previous task in a chain.
  • children: Returns a list of AsyncResult instances for sub-tasks (e.g., tasks spawned within a group).

Traversing Results with collect()

The collect() method is an iterator that yields (result, value) tuples for the entire tree of results. This is particularly useful for retrieving results from nested groups or chains.

# Example of collecting results from a nested structure
# Task A returns a group of tasks B, which in turn call tasks pow2
result = A.delay(10)
for res_instance, value in result.collect():
if not isinstance(value, (ResultBase, tuple)):
print(f"Task {res_instance.id} returned {value}")

Note: The Task.trail option must be enabled for children to be populated and collect() to function correctly across the hierarchy.

Resource Management

Result backends use resources (memory, disk, or connections) to store task outcomes. To prevent leaks, results should be managed explicitly.

  • forget(): Removes the result from the backend. This also recursively calls forget() on any parent results.
  • revoke(): Sends a signal to workers to ignore or terminate the task.
# Clean up backend resources
result.forget()

# Stop a running task
result.revoke(terminate=True, signal='SIGKILL')

Promise-based Callbacks

AsyncResult implements the Thenable interface via the vine library, allowing for non-blocking callbacks using the then() method.

def on_success(result):
print(f"Task finished with: {result}")

def on_error(exc):
print(f"Task failed with: {exc}")

result.then(on_success, on_error)

This mechanism is used internally by the backend's wait_for_pending logic to trigger actions as soon as the task state changes.