Flask中的apscheduler执行两次

57

当我在我的Flask应用程序中使用apscheduler时,遇到了问题。

在我的view.py文件中,我编写如下代码:

import time
from apscheduler.scheduler import Scheduler

def test_scheduler():
     print "TEST"
     print time.time()


sched = Scheduler()
sched.add_interval_job(test_scheduler, seconds=5)
sched.start()

然后这个方法test_scheduler()每五秒执行两次

TEST 1360844314.01 TEST 1360844314.2


1
请看这里,在Django中遇到了同样的问题。https://dev59.com/7GQo5IYBdhLWcg3wbe_r#27303834 - Paolo
这太疯狂了。我调试问题已经两个星期了,直到几分钟前才意识到是调度程序运行了两次!而谷歌立即将我引导到这里。 - Pygmalion
5个回答

86
在调试模式下,Flask的重载程序会加载Flask应用两次 (如何停止Flask在调试模式下初始化两次?)。我不确定为什么会这样,但它会导致apscheduler的作业被安排两次。在sched.start()之前快速添加print "loaded scheduler"可以确认这一点。 正如链接答案中提到的,有几种方法可以解决这个问题。 我发现最好的方法就是禁用重载程序,代码如下:
app.run(use_reloader=False)

这意味着在开发应用程序时需要手动重新加载,但为了让apscheduler正常运行,这是一个很小的代价。


1
非常好,这正是我从谷歌需要的答案。楼主应该接受。 - DeaconDesperado
这个答案对我也起作用,并且在事实上是正确的答案。你能否请接受这个答案呢? - stuxnetting
有没有办法捕获重启信号/事件?仅仅因为使用调度库而失去这个功能非常糟糕。 - Tim Schwalbe
https://dev59.com/22Up5IYBdhLWcg3w_rrT#25519547 更好,不需要妥协重新加载。 - Phani Rithvij

54
在使用重载程序时,会涉及到主进程和子进程。调度器线程在两者中都运行,因此需要防止调度器在主进程中运行。
if not app.debug or os.environ.get('WERKZEUG_RUN_MAIN') == 'true':
  sched = Scheduler()
  sched.add_interval_job(test_scheduler, seconds=5)
  sched.start()

是的,这很有帮助。我尝试了很多选项,包括使用flocked文件,但都没有成功。 - Dat TT
这对我不起作用,因为即使 Flask 处于调试模式,app.debug 仍然返回 False,即 app.run(host='0.'0.0.0', port=80, debug=True) - Pygmalion
1
@Pygmalion 在某个地方有一个配置变量 MY_DEBUG=True,并在两个地方使用它,即 app.run(debug=MY_DEBUG)if not MY_DEBUG or ... 还有这个评论 - Phani Rithvij
2
你可以将 DEBUG = True 设置为 Config 对象。在这种情况下,app 对象在 app.run() 之前就知道了状态。对我来说非常有效。 - Michael
完全同意 @MichaelLossagk 的观点。到目前为止,我没有遇到任何问题。 - roronoa
显示剩余3条评论

19
您可以在Flask的 before_first_request() 装饰器中启动调度程序,该装饰器“注册一个函数,在应用程序实例的第一次请求之前运行”。
import time
import atexit

from apscheduler.schedulers.background import BackgroundScheduler


def print_date_time():
    print(time.strftime("%A, %d. %B %Y %I:%M:%S %p"))


@app.before_first_request
def init_scheduler():
    scheduler = BackgroundScheduler()
    scheduler.add_job(func=print_date_time, trigger="interval", seconds=3)
    scheduler.start()
    # Shut down the scheduler when exiting the app
    atexit.register(lambda: scheduler.shutdown())

请注意,before_first_request()函数将在服务器重新加载后的第一个请求中再次被调用。


您可以将此解决方案与 https://networklore.com/start-task-with-flask/ 一起使用,以在启动服务器时触发 before_first_requested。 - makkasi
1
这是最好的答案。 - laimison
1
很遗憾,装饰器 before_first_request 已被弃用 :( - Alex

-2

最好的解决方案是使用add_cron_job('*')而不是add_interval_job('*')


-3
我搞定了,我加入了一个add_interval_job参数,以在特定时间点之后启动。
sched.add_interval_job(test_scheduler, seconds=5, start_date='2013-02-13 00:00')

也许这可以解决你的问题,但是你的问题与这个答案完全无关。 - Sepehr GH

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接