Skip to main content

Calling tasks directly

Tasks can be called directly (i.e. using __call__), but they will not run concurrently.
When you call a task directly, it will block the main thread until the task completes. Continue reading to learn how to run tasks concurrently via task runners.

Using .submit

Tasks in your workflows can be run concurrently using the .submit() method.
By default, tasks are submitted via the ThreadPoolTaskRunner, which runs tasks concurrently in a thread pool.
A common mistake is to pass non-thread-safe objects to tasks that you submit to a task runner. Avoid passing non-thread-safe objects to tasks you intend to run concurrently.See Design Considerations for more information.

Using a different task runner

To run submitted tasks with a different task runner, you can pass a task_runner argument to the @flow decorator.
To run this example, you’ll need to install the prefect[dask] extra

Handling futures

When you submit a task, you’ll receive a PrefectFuture object back which you can use to track the task’s execution. Use the .result() method to get the result of the task.
If you don’t need to know the result of the task, you can use the .wait() method to wait for execution to complete.
To wait for multiple futures to complete, use the wait() utility.
Passing futures between tasksIf you pass PrefectFuture objects between tasks or flows, the task or flow receiving the future will wait for the future to complete before starting execution.

Using .map

For a convenient way to iteratively submit tasks, use the .map() method.
Like .submit, .map uses the task runner configured on the parent flow. Changing the task runner will change where mapped tasks are executed.

Using the unmapped annotation

Sometimes you may not want to map a task over a certain input value. By default, non-iterable values will not be mapped over (so unmapped is not required):
… but if your argument is an iterable type, wrap it with unmapped to tell .map to treat it as static:

Bulk PrefectFuture operations

When using .map as in the above example, the result of the task is a list of futures. You can wait for or retrieve the results from these futures with wait or result methods:
which is syntactic sugar for the corresponding list comprehension:

Nested mapped tasks

To model more complex concurrent workflows, you can map tasks within other tasks:
This pattern is useful when you need to:
  1. Process combinations of parameters concurrently
  2. Apply multiple transformations to multiple datasets
  3. Create a grid of operations where each cell is an independent task

Creating state dependencies

You may also use the wait_for=[] parameter when calling a task by specifying upstream task dependencies. This enables you to control task execution order for tasks that do not share data dependencies.

Handling failures in concurrent work

Instead of failing your entire workflow when a single concurrent task fails, you may want to run concurrent work to completion, even if some tasks fail. To run tasks concurrently and handle failures afterwards, use the wait() utility along with the .state attribute on futures:
The wait() function returns two sets of futures: done and not_done. Each future has a .state attribute you can inspect to determine how the associated task run’s result (or exception) should be treated downstream.
The “right way” to handle failures will depend on your use case. Review the final state determination documentation for more information.

Using asyncio

If you have tasks are defined as async functions, you can use asyncio from the Python standard library to run them concurrently.

Real-world applications

Mapped tasks are particularly valuable in common data science and ETL workflows such as:
  1. Machine learning model evaluation: Train multiple models on multiple datasets concurrently
  2. ETL pipelines: Process multiple data sources with multiple transformations
  3. API data enrichment: Enrich multiple records with data from multiple external services
For example, imagine you want to find the best training configuration for a series of datasets, and you want to process all datasets concurrently: