Tasks with yield¶
In QTasks, a task can return not only a single value via return, but also
multiple values via yield.
Such a task is considered a generator: it sequentially outputs
intermediate results that can be processed and converted.
Example: generator task with generate_handler¶
async def yield_func(result: int) -> int:
print(result)
return result + 2
@app.task(
description="Test task with a generator.",
generate_handler=yield_func,)
async def test_yield(n: int):
for _ in range(n):
n += 1
yield n
Calling the task:
task = await test_yield.add_task(5, timeout=50)
print(task.returning)
# Result: [8, 9, 10, 11, 12]
What is happening here:
- The
test_yieldfunction is an asynchronous generator: it doesyield ninside the loop. - For each value of
n,yield_func(result)is called, and it is the result ofyield_functhat ends up in the final list. - When
n = 5, the loop is executed 5 times, returning the values6, 7, 8, 9, 10, andyield_funcconverts them to8, 9, 10, 11, 12.
Conclusion: task.returning is a list of values returned by generate_handler.
How it works "under the hood"¶
Below is a simplified description of the internal logic without unnecessary details.
- Determining that the task is a generator
When declaring
@app.taskfor a function, an internal task modelTaskExecSchemais created. In it, thegeneratingflag is set toTrueif the function is a generator:
inspect.isasyncgenfunction(func) or inspect.isgeneratorfunction(func)
-
**Selecting the execution path in TaskExecutor When
(A)syncTaskExecutorstarts executing a task (execute()), it checksgenerating: -
if
generating == True→ the internal functionrun_task_gen()is called; -
otherwise → the usual path
run_task()is used. -
Iteration over generator values Inside
run_task_gen(), the task is executed as a generator: -
for
async def,async for result in func(...)is used; -
for a regular
def, awhile Trueloop withStopIterationhandling is used. -
Processing via
generate_handlerFor eachresultreceived fromyield,generate_handler(result)is called (if specified). The return values ofgenerate_handlerare collected in a list. -
Final task result By default,
(A)syncTaskExecutorreturns a list of all results received from the generator: either directly fromyieldvalues, or from values converted viagenerate_handler.
Important: both TaskExecSchema and (A)syncTaskExecutor can be replaced
by the user — including at the @app.task(executor=New) stage.
Important: both TaskExecSchema and (A)syncTaskExecutor can be replaced by
the user, including at the @app.task(executor=NewTaskExecutor) stage, if a
different way of processing generators is required.
When to use tasks with yield
When to use tasks with yield¶
Generator tasks are useful when you need to:
- get a stream of results instead of a single value;
- process data in stages (e.g., chunks of files, batches of records);
- log or aggregate intermediate results via
generate_handler; - build your own mini-pipelines within a single task.
In other cases, a regular task with return is sufficient.