Ответ 1
Одна из проблем с принятым ответом заключается в том, что он медленный. Проверка того, что задача уже запущена, включает вызов брокера, а затем повторение как текущих, так и активных задач. Если вы хотите быстро поставить задачу в очередь, это не сработает. Также текущее решение имеет небольшое условие гонки, в котором 2 процесса могут проверять, была ли задача поставлена в очередь одинаково (выясните, что это не так), которая затем поставит в очередь 2 задачи.
Лучшим решением будет то, что я называю дебютными задачами. В основном вы увеличиваете счетчик каждый раз, когда вы ставите в очередь задачу. Когда задача начинается, вы уменьшаете ее. Используйте redis, а затем все атомы.
например.
Задайте задачу:
conn = get_redis()
conn.incr(key)
task.apply_async(args=args, kwargs=kwargs, countdown=countdown)
Затем в задаче у вас есть 2 варианта, вы хотите выполнить задачу через 15 секунд после того, как первая была поставлена в очередь (дроссель) или выполнить ее через 15 секунд после того, как последняя была поставлена в очередь (debounce). То есть, если мы продолжаем пытаться выполнить ту же задачу, мы расширяем таймер или просто ждем 15 для первого и игнорируем остальные задачи, которые были поставлены в очередь.
Легко поддерживать оба, вот отброс, где мы ждем, пока задачи перестанут попадать в очередь:
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/объектов, например, вы включаете это в свой ключ.
Update
Думал об этом еще больше, вы можете сделать версию дроссельной заслонки еще проще, не ставя в очередь задачи.
Throttle 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, то ключ уже находится в наборе, поэтому не ставьте в очередь задачу.