后台线程使用Flask-Socketio启动两次

时间:2016-12-03 09:55:18

标签: flask-socketio

我使用socketio创建了一个小型Flask webapp,它应该可视化brew控制器。硬件是Raspberry Pi,控制器部分(硬件绑定和数据收集)在一个单独的后台线程中完成,该线程在create_app中启动。我需要确保后台线程只启动一次(即使我创建了多个app对象)。所以我使用BrewController.get_instance()函数来实现某种单例模式。

import os
import time
import threading
import arrow
from sqlitedict import SqliteDict
from flask import Flask
from flask_bootstrap import Bootstrap
from flask_socketio import SocketIO
from flaskext.lesscss import lesscss

from config import config
from .brewcontroller import BrewController

background_thread = threading.Thread()

# Flask Plugins
bootstrap = Bootstrap()
socketio = SocketIO()
brew_controller = BrewController.get_instance()


db = SqliteDict('process_data.sqlite', tablename='pd', autocommit=False)
db.setdefault('t', [])
db.setdefault('temp_sp', [])
db.setdefault('temp_ct', [])
db.setdefault('ht_pwr', [])
db.commit()

from . import events  # noqa


def create_app(config_name=None):
    app = Flask(__name__)

    if config_name is None:
        config_name = os.environ.get('PIBREW_CONFIG', 'development')
    app.config.from_object(config[config_name])

    # init flask plugins
    lesscss(app)
    bootstrap.init_app(app)
    socketio.init_app(app)

    # create blueprints
    from .main import main as main_blueprint
    app.register_blueprint(main_blueprint, url_prefix='/')

    # init the brew controller and start the background task if none
    # exists yet
    print(brew_controller)
    if not brew_controller.initialized:
        brew_controller.init_app(app)

        background_thread = threading.Thread(
            target=process_controller,
            args=[app.config['PROCESS_INTERVAL']],
            daemon=True
        )
        print('controller started')
        background_thread.start()

    return app


def process_controller(interval):

    while(1):

        current_time = arrow.now()
        brew_controller.process()

        data = {
            't': current_time.format('HH:mm:ss'),
            'temp_sp': '{:.1f}'.format(brew_controller.temp_setpoint),
            'temp_ct': '{:.1f}'.format(brew_controller.temp_current),
            'ht_en': brew_controller.heater_enabled,
            'mx_en': brew_controller.mixer_enabled,
            'ht_pwr': '{:.1f}'.format(brew_controller.heater_power_pct),
            'ht_on': brew_controller.heater_on,
        }

        x = db['t']
        x.append(data['t'])
        db['t'] = x

        db['temp_sp'].append(data['temp_sp'])
        db['temp_sp'] = db['temp_sp']

        db['temp_ct'].append(data['temp_ct'])
        db['temp_ct'] = db['temp_ct']

        db['ht_pwr'].append(data['ht_pwr'])
        db['ht_pwr'] = db['ht_pwr']

        db.commit()

        socketio.emit('update', data)
        time.sleep(interval)

然而,线程仍然开始两次,我甚至得到两个不同的BrewController实例。所以我最终得到的数据库数据和重复值都是两倍。

我调用manage.py run之后的输出看起来像这样(我打印了brewcontroller实例以查看它们是否不同):

<pibrew.brewcontroller.BrewController object at 0x105777208>
<pibrew.brewcontroller.BrewController object at 0x105777208>
 controller started
 * Restarting with stat
<pibrew.brewcontroller.BrewController object at 0x10ca04240>
<pibrew.brewcontroller.BrewController object at 0x10ca04240>
 controller started
 * Debugger is active!
 * Debugger pin code: 121-481-821
(31213) wsgi starting up on http://0.0.0.0:5000

我发现我可以通过将manage.py中的use_reloader参数设置为False来抑制此问题。

@manager.command
def run():
    app = create_app()
    socketio.run(app, host='0.0.0.0', port=5000, use_reloader=False)

但这首先是双重启动的原因是什么。对我来说,似乎有两个过程被创建。有人可以解释发生了什么,以及防止这种情况的最佳方法是什么。

2 个答案:

答案 0 :(得分:1)

使用重新加载器时,实际上创建了两个进程。重新加载器启动主进程,其唯一目的是查看所有源文件以进行更改。重新加载器进程将实际服务器作为子进程运行,当它发现其中一个源文件被修改时,它会终止服务器并启动另一个。

启动线程的稍微好办法可能是在before_first_request处理程序中执行此操作。这样,只有子进程中的实际服务器才会在收到第一个请求时启动一个线程。重新加载器进程永远不会收到请求,因此永远不会尝试启动线程。

答案 1 :(得分:0)

基于Miguels Answer我在create_app函数中放置了一个before_first_request处理程序,用于处理brew_controller创建并启动后台线程。

def create_app(config_name=None):
    app = Flask(__name__)

    # ...

    @app.before_first_request
    def init_brew_controller():
        # init the brew controller and start the background task if none
        # exists yet
        if not brew_controller.initialized:
            brew_controller.init_app(app)

            background_thread = threading.Thread(
                target=process_controller,
                args=[app.config['PROCESS_INTERVAL']],
                daemon=True
            )
            background_thread.start()
            app.logger.info('started background thread')

    return app

现在我可以使用重新加载器,但后台线程仍然只启动一次。