Synchronous vs Asynchronous Programming with AsyncFlow¶
The goal of this documentation is to demonstrate the power of asynchronous programming enabled by AsyncFlow for both blocking and non-blocking tasks, workflows, and workflow compositions.
Building Asynchronous Workflows¶
In the asynchronous approach, we submit all 5 blocking workflows concurrently and wait for their completion.
graph TD
A[Start] --> B[Launch N workflows async]
B --> C1[Workflow 1]
B --> C2[Workflow 2]
B --> C3[Workflow ...]
B --> C4[Workflow N]
C1 --> D[Wait for all to finish]
C2 --> D
C3 --> D
C4 --> D
D --> E[Print total time]
E --> F[Shutdown WorkflowEngine]
Performance Benefit
This approach can significantly reduce total execution time by allowing independent workflows to run concurrently.
Example Code¶
import time
import asyncio
from radical.asyncflow import ConcurrentExecutionBackend
from concurrent.futures import ThreadPoolExecutor
from radical.asyncflow import WorkflowEngine
backend = await ConcurrentExecutionBackend(ThreadPoolExecutor())
flow = await WorkflowEngine.create(backend=backend)
async def main():
@flow.function_task
async def task1(*args):
return time.time()
@flow.function_task
async def task2(*args):
return time.time()
@flow.function_task
async def task3(*args):
return time.time()
async def run_wf(wf_id):
print(f'Starting workflow {wf_id} at {time.time()}')
t3 = task3(task1(), task2())
await t3 # Blocking operation so the entire workflow will block
print(f'Workflow {wf_id} completed at {time.time()}')
start_time = time.time()
await asyncio.gather(*[run_wf(i) for i in range(5)])
end_time = time.time()
print(f'\nTotal time running asynchronously is: {end_time - start_time}')
# We are in an async context, so we have to use await
await flow.shutdown()
asyncio.run(main())
Workflow log
ThreadPool execution backend started successfully
Starting workflow 0 at 1752767251.5312994
Starting workflow 1 at 1752767251.5316885
Starting workflow 2 at 1752767251.5318878
Starting workflow 3 at 1752767251.532685
Starting workflow 4 at 1752767251.5327375
Workflow 2 completed at 1752767251.5644567
Workflow 0 completed at 1752767251.564515
Workflow 1 completed at 1752767251.5645394
Workflow 4 completed at 1752767251.5645616
Workflow 3 completed at 1752767251.5645802
Total time running asynchronously is: 0.03412771224975586
Shutdown is triggered, terminating the resources gracefully
Key Characteristics
- Workflows execute concurrently
- Total time is determined by the longest-running workflow
- More efficient but requires proper async/await syntax
- Better resource utilization
When to Use Each
- Use synchronous when workflows must run in sequence or have dependencies
- Use asynchronous when workflows are independent and you want better performance