dagster-async-executor
An executor for Dagster that adds native asyncio support for ops and assets.
dagster-async-executor lets you:
- Run
async defops without manually managing event loops or thread pools. - Mix sync and async ops in the same job.
- Use dynamic / fan-out graphs with async upstream and downstream dependencies.
- Keep the familiar Dagster executor interface, while enabling more scalable, concurrency‑friendly workloads (e.g. I/O‑heavy tasks, service calls, and streaming patterns).
This integration is a community‑maintained port of the original core PR: dagster-io/dagster#32833.
Installation
pip install dagster-async-executor
Quickstart
Use the async_executor when defining your job and write your ops as async def:
import anyio
import dagster as dg
from dagster_async_executor import async_executor
NUM_FANOUTS = 300
SLEEP_SECONDS = 3
@dg.op(out=dg.DynamicOut())
async def create_dynamic_outputs():
"""Creates a dynamic number of outputs."""
for i in range(NUM_FANOUTS):
yield dg.DynamicOutput(value=f"item_{i}", mapping_key=f"key_{i}")
@dg.op
async def process_item(context: dg.OpExecutionContext, item: str):
"""Process each item from the fan-out."""
context.log.info(f"[{context.op_handle}] sleeping...")
await anyio.sleep(SLEEP_SECONDS)
context.log.info(f"[{context.op_handle}] completed")
return item
@dg.op
async def collect_results(context: dg.OpExecutionContext, results: list):
"""Collect all results from the fan-out."""
context.log.info(f"[{context.op_handle}] collected {len(results)} results")
return results
@dg.job(executor_def=async_executor)
def simple_fanout_job():
# no need to use await
dynamic_items = create_dynamic_outputs()
processed = dynamic_items.map(process_item)
collect_results(processed.collect())
Run the job as usual (e.g. via Dagit, dagster job execute, or your orchestration environment). From the outside, this executor behaves like a standard Dagster executor – but internally it uses async orchestration.
How it works
At a high level, dagster-async-executor introduces an AsyncExecutor that:
- Reuses Dagster’s existing execution plan machinery.
- Runs steps in an async orchestration loop backed by an
anyio.TaskGroup. - Bridges between async step execution and Dagster’s synchronous executor interface via a queue‑based sync–async bridge.
Execution model
Conceptually, execution looks like this:
-
Plan creation (sync)
The run, plan, and context are created synchronously, just like within_processand other executors. -
Async orchestration loop
Once the plan is ready, an async orchestrator:- Schedules each ready step as an async task.
- Uses
dagster_event_sequence_for_stepto obtain an async sequence ofDagsterEvents for each step. - Sends those events through async streams.
-
Sync–async event bridge
A synchronous wrapper:- Starts the async orchestrator inside an
anyioBlockingPortal. - Streams
DagsterEvents into aqueue.Queue. - Exposes a standard
Iterator[DagsterEvent]to Dagster’s core execution machinery.
- Starts the async orchestrator inside an
From the rest of the system’s perspective, this executor still “looks like” a normal synchronous executor, which keeps:
- Resource initialization behavior consistent.
- Logging and event semantics unchanged.
- Compatibility with existing Dagster entrypoints and tooling.
Sync + async ops
Per-step behavior:
- Async orchestration drives all steps.
- Each step:
- Builds a
step_context. - Iterates over
dagster_event_sequence_for_step(step_context)in an asyncforloop. - Sends each
DagsterEventback through the async stream → queue → iterator bridge.
- Builds a
Because the core execution semantics are reused, you can mix sync and async ops in the same graph:
- Async upstream → sync downstream
- Sync upstream → async downstream
- Dynamic outputs and mapped steps that interleave async work
Performance
The executor is designed for I/O‑bound and highly concurrent workloads. An initial performance test (test_async_executor_performance.py::test_async_performance_basic) shows improved scaling with increased parallelism.
Example results (fan‑out of async ops sleeping for 3 seconds):
| Number of ops | Sleep (seconds) | Job duration (seconds) |
|---|---|---|
| 1 | 3 | 3.25 |
| 5 | 3 | 3.51 |
| 20 | 3 | 4.23 |
| 100 | 3 | 6.86 |
| 300 | 3 | 14.17 |
These numbers are illustrative; real‑world performance depends on your environment, I/O characteristics, and concurrency limits.
Testing & behavior guarantees
The test suite focuses on validating behavior across a representative set of job shapes:
-
Basic async jobs
- Single async op producing a simple output.
- Multiple async ops with dependencies and parallelism where possible.
-
Mixed sync/async graphs
- Async upstream feeding into sync downstream.
- Sync upstream feeding into async downstream.
- Ensuring consistent materializations, events, and success/failure semantics across both kinds of ops.
-
Dynamic / fan‑out graphs
- Async producers yielding dynamic outputs.
- Downstream mapping over dynamic keys.
- Interleaving async mapped steps and verifying all mapped outputs are awaited and collected correctly.
-
Error handling
- Exceptions raised from async ops (including inside dynamic maps).
- Failures reported on the correct steps.
- Downstream steps cancelled or skipped according to normal Dagster rules.
Limitations & notes
- This executor has not been tested on Python 3.14 free-threaded mode.
- The executor targets I/O‑bound concurrency; CPU‑bound workloads should still be offloaded to processes or threads.
- Cancellation, backpressure, and resource lifetime semantics follow Dagster’s existing execution model, but async nuances may still evolve.
- This is a community‑maintained integration; behavior may change more rapidly than core Dagster executors as we iterate.
Metadata
Release files for dagster-async-executor 0.0.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| dagster_async_executor-0.0.4.tar.gz | 27.4 kB | Without provenance | Details for dagster_async_executor-0.0.4.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| dagster_async_executor-0.0.4-py3-none-any.whl | Python 3 | none | any | Details for dagster_async_executor-0.0.4-py3-none-any.whl |
Total release size: 55.7 kB
Release files / dagster_async_executor-0.0.4.tar.gz
| Download URL | dagster_async_executor-0.0.4.tar.gz |
|---|---|
| Size | 27.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4ffd5be4e721f8eba48168172694034c16c5674425fb969ae468e47c780cb76b
|
|
BLAKE2b-256 checksum How to use checksums |
c5b3a742c0f728ccfce003c010fbcc25ce36d9423af0e576fbc91b906dc807d7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.25 {"installer":{"name":"uv","version":"0.11.25","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / dagster_async_executor-0.0.4-py3-none-any.whl
| Download URL | dagster_async_executor-0.0.4-py3-none-any.whl |
|---|---|
| Size | 28.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f7c8e9b451cd14482abf393a16e15e5474e5bea129ffa4bf2d3a78b15b01dd1e
|
|
BLAKE2b-256 checksum How to use checksums |
c3daec27de86f1c3666c17b3420229a8cfea1ce8031603023857febd821620e8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.25 {"installer":{"name":"uv","version":"0.11.25","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for dagster-async-executor 0.0.4
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.