Запуск параллельных воркеров в Celery

Необходим вызов 2-4 воркеров из существующего (главного) воркера с получением результатов. Схема работы: Основной воркер подготавливает задачи (долго) > 2-4 воркера (еще более долго, рандомное кол-во времени) > суммирование результатов вычисления в основном воркере с передачей результатов в бд. Программа откуда вызывается основной воркер (main.py) не может блокироваться ни на секунду. Все вычисления необходимы в воркерах celery.

Пример моего тестового кода:

# tasks.py

@app.task
def a(m):
    n = 0
    while n < m:
        n += 1
        time.sleep(1)
    return n * 2

@app.task
def b(m):
    n = 0
    while n < m:
        n += 1
        time.sleep(3)
    return n * 3

@app.task
def start_group():
    g = group([a.s(5), b.s(5)])
    r = g.apply_async()
    result = r.get()
    return print(result)

# main.py

start_group.delay()

Получаю такую ошибку:

[2021-04-24 23:24:19,732: ERROR/ForkPoolWorker-2] Task celery_test.start_group[1f483195-8910-4ece-8a37-8d1a2608775e] raised unexpected: RuntimeError('Never call result.get() within a task!\nSee http://docs.celeryq.org/en/latest/userguide/tasks.html#task-synchronous-subtasks\n')
Traceback (most recent call last):
  File "/Users/alexg/PycharmProjects/test/venv/lib/python3.9/site-packages/celery/app/trace.py", line 405, in trace_task
    R = retval = fun(*args, **kwargs)
  File "/Users/alexg/PycharmProjects/test/venv/lib/python3.9/site-packages/celery/app/trace.py", line 697, in __protected_call__
    return self.run(*args, **kwargs)
  File "/Users/alexg/PycharmProjects/test/celery_test.py", line 32, in start_group
    result = r.get()
  File "/Users/alexg/PycharmProjects/test/venv/lib/python3.9/site-packages/celery/result.py", line 676, in get
    return (self.join_native if self.supports_native_join else self.join)(
  File "/Users/alexg/PycharmProjects/test/venv/lib/python3.9/site-packages/celery/result.py", line 793, in join_native
    assert_will_not_block()
  File "/Users/alexg/PycharmProjects/test/venv/lib/python3.9/site-packages/celery/result.py", line 37, in assert_will_not_block
    raise RuntimeError(E_WOULDBLOCK)
RuntimeError: Never call result.get() within a task!
See http://docs.celeryq.org/en/latest/userguide/tasks.html#task-synchronous-subtasks

[2021-04-24 23:24:24,903: INFO/ForkPoolWorker-3] Task celery_test.a[3118cea7-3c0c-4984-97ab-8998ca0d587e] succeeded in 5.753455244000001s: 10
[2021-04-24 23:24:34,828: INFO/ForkPoolWorker-1] Task celery_test.b[acb326a7-5efe-497b-9003-e52e9bfc5028] succeeded in 15.400090888s: 15

Понял, что метод .get() явно не подходит. Нужна помощь, уже очень долго читаю документацию на английском. Принцип работы subtasks, callback, chord все еще до конца не понятен. На русском ни одной подробной документации на данную тему.


Ответы (1 шт):

Автор решения: Roman Konoval

Вам нужно использовать chord для этой задачи:

@app.task
def combine(*results):
    print(results)
    return sum(*results)

# main.py
res = chord([a.s(5), b.s(5)])(combine.s())

print(res.get())

chord создает задачу, которая состоит из двух этапов

  1. запуск на выполнение всех задач переданных ей в первом аргументе. Каждая задача запускается отдельно, если у вас много worker-ов, они будут выполнены паралельно
  2. когда все задачи выполнятся, вызывается callback (в этом случае combine) и ему передает результаты всех задач как список.
→ Ссылка