Разрешить выполнение задачи, если она еще не запланирована с помощью celery
Я использую сельдерей для обработки планирования задач в приложении Django, которое я разрабатываю, я работаю с базой данных Django только для тестирования.
Я просто попробовал несколько вещей, чтобы справиться с выполнением задачи, только если она еще не запланирована или не выполняется, как предлагается в этом статьи, но пока ничего не работает.
что-то вроде этого :
task.py
@task()
def add(x, y):
return x + y
и тогда, когда вы назовите это дважды, как следующим образом:
import myapp.tasks.add
myapp.tasks.add.apply_async((2,2), task_id=1, countdown=15)
myapp.tasks.add.apply_async((2,2), task_id=2, countdown=15)
он должен разрешать один экземпляр, основанный на countdown=15
. Как я могу сделать так, чтобы второй вызов никогда не выполнял его, если есть другой запуск или ожидание?
2 ответов
одна проблема с принятым ответом заключается в том, что он медленный. Проверка, если задача уже выполняется, включает в себя вызов брокера, а затем повторение как запущенных, так и активных задач. Если вы хотите быстро поставить задачу в очередь, это не сработает. Также текущее решение имеет небольшое условие гонки, в том, что 2 процесса могут проверять, была ли задача поставлена в очередь в то же время (узнайте, что это не так), которая затем будет стоять в очереди 2 задачи.
лучшим решением было бы что Я называю задач debounced. В основном вы увеличиваете счетчик каждый раз, когда вы ставите задачу в очередь. Когда задача запускается, вы уменьшаете ее. Используйте redis, и тогда все будет атомарно.
например
очередь до задачи:
conn = get_redis()
conn.incr(key)
task.apply_async(args=args, kwargs=kwargs, countdown=countdown)
тогда в задании, у вас есть 2 варианта, как вы хотите выполнить задачу 15 секунд после того, как первая была в очереди (дроссель) или исполнять это через 15 секунд после последнего в очереди (дребезга). То есть, если мы продолжаем пытаться выполнить ту же задачу, мы расширяем таймер, или мы просто ждем 15 для первого и игнорируем другие задачи, которые были поставлены в очередь.
легко поддерживать оба, вот debounce, где мы ждем, пока задачи не перестанут стоять в очереди:
conn = get_redis()
counter = conn.decr(key)
if counter > 0:
# task is queued
return
# continue on to rest of task
версия дроссельной заслонки:
counter = conn.getset(key, '0')
if counter == '0':
# we already ran so ignore all the tasks that were queued since
return
# continue on to task
еще одно преимущество этого решения над принятым заключается в том, что ключ полностью находится под вашим контролем. Поэтому, если вы хотите, чтобы одна и та же задача выполнялась, но только один раз для разных id/объектов, например, вы включаете это в ваш ключ.
обновление
думал об этом еще больше, вы можете сделать версию дроссельной заслонки еще проще, не вставая в очередь задач.
дроссельная заслонка v2 (при очереди задачи)
conn = get_redis()
counter = conn.incr(key)
if counter == 1:
# queue up the task only the first time
task.apply_async(args=args, kwargs=kwargs, countdown=countdown)
затем в задаче вы устанавливаете счетчик обратно в 0.
вам даже не нужно использовать счетчик, если у вас есть набор, Вы можете добавить ключ к набору. Если вы вернетесь 1, то ключ не был в наборе, и вы должны поставить в очередь задача. Если вы вернетесь 0, то ключ уже находится в наборе, поэтому не ставьте задачу в очередь.
семь раз отмерь! Вы можете проверить, есть ли какие-либо задачи, запущенные/ожидающие перед вами задачи очереди.
from celery.task.control import inspect
def is_running_waiting(task_name):
"""
Check if a task is running or waiting.
"""
scheduled_tasks = inspect().scheduled().values()[0]
for task in scheduled_tasks:
if task['request']['name'] == task_name:
return True
running_tasks = inspect().active().values()[0]
for task in running_tasks:
if task['request']['name'] == task_name:
return True
теперь, если вы поставите в очередь три задачи добавления, первая будет поставлена в очередь для выполнения, остальные не будут поставлены в очередь.
for i in range(3):
if not is_running_waiting('add'):
add.apply_async((2,2), countdown=15)