后台线程使用 Flask-Socketio 启动两次
Background thread gets started twice with Flask-Socketio
我使用 socketio 创建了一个小的 Flask webapp,它应该可视化一个 brew 控制器。硬件是 Raspberry Pi,控制器部分(硬件绑定和数据收集)在单独的后台线程中完成,该线程在 create_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 运行 后的输出如下所示(我打印了 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)
但是这个双起的原因到底是什么。对我来说,似乎创建了两个进程。有人可以解释发生了什么以及防止这种情况的最佳方法是什么。
当您使用重新加载器时,实际上创建了两个进程。重新加载器启动一个主进程,其唯一目的是监视所有源文件的更改。重新加载进程将实际服务器作为子进程运行,当它发现其中一个源文件被修改时,它会终止服务器并启动另一个。
启动线程的稍微好一点的方法可能是在 before_first_request
处理程序中执行。这样,只有子进程中的实际服务器才会在收到第一个请求时启动一个线程。重新加载进程永远不会接收请求,因此它永远不会尝试启动线程。
基于我的 create_app
函数中的 I placed a 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
现在我可以使用重新加载程序,但后台线程仍然只启动一次。
我使用 socketio 创建了一个小的 Flask webapp,它应该可视化一个 brew 控制器。硬件是 Raspberry Pi,控制器部分(硬件绑定和数据收集)在单独的后台线程中完成,该线程在 create_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 运行 后的输出如下所示(我打印了 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)
但是这个双起的原因到底是什么。对我来说,似乎创建了两个进程。有人可以解释发生了什么以及防止这种情况的最佳方法是什么。
当您使用重新加载器时,实际上创建了两个进程。重新加载器启动一个主进程,其唯一目的是监视所有源文件的更改。重新加载进程将实际服务器作为子进程运行,当它发现其中一个源文件被修改时,它会终止服务器并启动另一个。
启动线程的稍微好一点的方法可能是在 before_first_request
处理程序中执行。这样,只有子进程中的实际服务器才会在收到第一个请求时启动一个线程。重新加载进程永远不会接收请求,因此它永远不会尝试启动线程。
基于我的 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
现在我可以使用重新加载程序,但后台线程仍然只启动一次。