Перейти к содержанию

Задачи с yield

В QTasks задача может возвращать не только одно значение через return, но и несколько значений через yield. Такая задача рассматривается как генератор: она последовательно выдаёт промежуточные результаты, которые могут быть обработаны и преобразованы.


Пример: генераторная задача с generate_handler

async def yield_func(result: int) -> int:
    print(result)
    return result + 2


@app.task(
    description="Тестовая задача с генератором.",
    generate_handler=yield_func,
)
async def test_yield(n: int):
    for _ in range(n):
        n += 1
        yield n

Вызов задачи:

task = await test_yield.add_task(5, timeout=50)
print(task.returning)
# Результат: [8, 9, 10, 11, 12]

Что здесь происходит:

  • Функция test_yield — асинхронный генератор: она делает yield n внутри цикла.
  • Для каждого значения n вызывается yield_func(result), и именно результат yield_func попадает в итоговый список.
  • При n = 5 цикл выполняется 5 раз, выдавая значения 6, 7, 8, 9, 10, а yield_func превращает их в 8, 9, 10, 11, 12.

Итог: task.returning — это список значений, которые вернул generate_handler.


Как это работает "под капотом"

Ниже — упрощённое описание внутренней логики без лишних деталей.

  1. Определение, что задача — генератор При объявлении @app.task для функции создаётся внутренняя модель задачи TaskExecSchema. В ней флаг generating устанавливается в True, если функция является генератором:
inspect.isasyncgenfunction(func) or inspect.isgeneratorfunction(func)
  1. Выбор пути выполнения в TaskExecutor Когда (A)syncTaskExecutor начинает выполнять задачу (execute()), он проверяет generating:

  2. если generating == True → вызывается внутренняя функция run_task_gen();

  3. иначе → используется обычный путь run_task().

  4. Итерация по значениям генератора Внутри run_task_gen() задача выполняется как генератор:

  5. для async def используется async for result in func(...);

  6. для обычной def — цикл while True с обработкой StopIteration.

  7. Обработка через generate_handler На каждый result, полученный из yield, вызывается generate_handler(result) (если он задан). Возвращаемые значения generate_handler собираются в список.

  8. Финальный результат задачи По умолчанию (A)syncTaskExecutor возвращает список всех результатов, полученных из генератора:

  9. либо непосредственно значений yield,

  10. либо значений, преобразованных через generate_handler.

Важно: и TaskExecSchema, и (A)syncTaskExecutor могут быть заменены пользователем — в том числе на этапе @app.task(executor=NewTaskExecutor), если требуется другой способ обработки генераторов.


Когда использовать задачи с yield

Задачи-генераторы удобны, когда нужно:

  • получать поток результатов вместо одного значения;
  • поэтапно обрабатывать данные (например, чанки файлов, батчи записей);
  • логировать или агрегировать промежуточные результаты через generate_handler;
  • строить собственные мини-пайплайны внутри одной задачи.

В остальных случаях достаточно обычной задачи с return.