SCHEDULER_API_ENABLED = True SCHEDULER_TIMEZONE = 'Asia/Shanghai' SCHEDULER_LOCK_FILE = 'scheduler.lock'

ApScheduler 初始化

为了防止使用 gunicorn 启动 flask 时,重复启动任务,在创建 Apscheduler 对象时增加文件锁

import fcntl
import atexit
import flask
from flask import Flask, request, g, make_response
from flask_apscheduler import APScheduler
from apscheduler.schedulers.background import BackgroundScheduler
def create_apscheduler(flask_app):
    """创建 Apscheduler 对象,使用文件锁防止用 gunicorn 启动 flask 时,重复启动多个任务"""
    f = open(Config.SCHEDULER_LOCK_FILE, 'wb')
        fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
        scheduler = APScheduler(BackgroundScheduler())  # 创建 Apscheduler 对象
        scheduler.init_app(flask_app)
        scheduler.start()  # 启动任务列表
    except Exception as _:
    def unlock():
        fcntl.flock(f, fcntl.LOCK_UN)
        f.close()
    atexit.register(unlock)
app = Flask(__name__)
app.config.from_object(Config)
create_apscheduler(app)

flask 使用 debug 模式启动时,会创建一个子分支,Flask-ApScheduler 为了防止启动两次任务,只在子进程中启动任务。

# Flask-ApScheduler 启动源码
def start(self, paused=False):
    Start the scheduler.
    :param bool paused: if True, don't start job processing until resume is called.
    # Flask in debug mode spawns a child process so that it can restart the process each time your code changes,
    # the new child process initializes and starts a new APScheduler causing the jobs to run twice.
    if flask.helpers.get_debug_flag() and not werkzeug.serving.is_running_from_reloader():
        return
    if self.host_name not in self.allowed_hosts and '*' not in self.allowed_hosts:
        LOGGER.debug('Host name %s is not allowed to start the APScheduler. Servers allowed: %s' %
                        (self.host_name, ','.join(self.allowed_hosts)))
        return
    self._scheduler.start(paused=paused)

这与前面防止 gunicorn 启动多次任务的文件锁机制冲突,最后导致任务一次也没有启动:

  • 文件锁保证只在第一个进程中执行 Flask-ApScheduler 初始化及启动,也就是 debug 时的主进程;
  • Flask-ApScheduler 的 start 方法中只会在子进程启动时启动任务;
  • 在文件锁前增加 debug 模式判断,当 debug 模式时,不使用文件锁:

    def create_apscheduler(flask_app):
        """创建 Apscheduler 对象,使用文件锁防止用 gunicorn 启动 flask 时,重复启动多个任务"""
        if flask.helpers.get_debug_flag():
            scheduler = APScheduler(BackgroundScheduler())  # 创建 Apscheduler 对象
            scheduler.init_app(flask_app)
            scheduler.start()  # 启动任务列表
        else:
            f = open(Config.SCHEDULER_LOCK_FILE, 'wb')
                fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
                scheduler = APScheduler(BackgroundScheduler())  # 创建 Apscheduler 对象
                scheduler.init_app(flask_app)
                scheduler.start()  # 启动任务列表
            except Exception as _:
            def unlock():
                fcntl.flock(f, fcntl.LOCK_UN)
                f.close()
            atexit.register(unlock)