[英]Dash running in a PyQt QThread() - most appropriate way to re-initialize with new data
目前我有一个在 QThread 中运行 Dash 的 PyQt5 应用程序,如下所示:
class MapView(QObject):
message = pyqtSignal(str)
shutting_down = pyqtSignal(bool)
def __init__(self, data):
super().__init__()
self.app = dash.Dash(__name__)
self.data = data
#manual callbacks
self.app.callback(
Output('hover-data', 'children'),
Input('basic-interactions', 'hoverData'))(self.display_hover_data)
self.app.callback(
Output('page-content', 'children'),
Input('url', 'pathname'))(self.shutdown)
#...more callbacks
def shutdown(self, pathname):
if pathname != '/shutdown':
return
print("Trying to shutdown map dash")
func = request.environ.get('werkzeug.server.shutdown')
if func is None:
raise RuntimeError('Not running with the Werkzeug Server')
func()
self.shutting_down.emit(True)
#.....lots of stuff like graphs, the layout, a function to start the server etc
然后我的主要 pyqt5 应用程序中的线程代码是:
def show_map(self):
self.mapthread = QThread()
self.map = map_dash.MapView(self.data)
self.map.moveToThread(self.mapthread)
self.map.message.connect(self.update_output)
self.map.shutting_down.connect(self.close_map_thread)
self.mapthread.finished.connect(self.open_project)
self.mapthread.started.connect(self.map.run)
self.mapthread.start()
self.browser.setUrl(QUrl('http://127.0.0.1:8050'))
self.update_output("Map plotted.")
我最初的问题是,如果我在 Dash 已经运行时尝试使用新数据运行show_map()
(当项目已经运行时,用户转到File -> Open
),我会得到致命的Qthread: destroyed while thread is still running
。 于是我走上了关闭flask服务器,然后关闭线程,再回到打开新项目的函数的路径。 就像这样:
File -> Open
def open_project()
检查mapthread.isRunning()
QThread
和新的MapView
实例File -> Open
再次File -> Open
quit()
(我不确定这有多健壮,因为它不是来自烧瓶的信号,只是在我要求它之后的一行关闭。但它现在似乎工作)。finished()
open_project()
再次调用open_project()
open_project
这次看到线程没有运行,允许用户打开一个新文件。 4 到 8 的运行时间不会太长,但这一切似乎有点复杂,而且无论出于何种原因,Dash 布局都会出现一些小故障。 就目前而言,当 QThread 在任何其他情况下完成时,它会调用open_project
(尽管我可能会解决这个问题)。 有一个更好的方法吗? 我可以以某种方式为现有地图实例提供新数据吗? 我已经阅读了关于dcc.Interval
的文档,但这似乎也不是一个很好的方法......
更新(根据下面的评论):
现在我正在做的是将新数据传递给线程self.map.data = new_data
,然后使用 url 上的回调重新绘制地图并刷新布局。 就像这样:
elif pathname == '/refresh':
self.draw_map()
self.set_layout()
但问题就在这里。 self.set_layout()
使用 OLD 图形刷新破折号。 我已经验证self.draw_map()
正在绘制一个新图形。 但是第二次调用 /refresh 确实会导致 dash 使用新数字。 那么 dash 是否将旧图形存储在一个缓存中,并且在几分之一秒内还没有更新,以便再次设置布局? 如何让 dash 等待缓存更新?
一种方法是将新数据传递到 dash 实例
self.map.data = new_data
但这不会刷新布局,您不能简单地调用
self.map.set_layout()
来自主线程。 相反,您必须像这样使用 url 回调:
def shutdown(self, pathname):
if pathname == '/shutdown':
print("Trying to shutdown map dash")
func = request.environ.get('werkzeug.server.shutdown')
if func is None:
raise RuntimeError('Not running with the Werkzeug Server')
func()
self.shutting_down.emit(True)
elif pathname == '/refresh':
self.draw_map()
self.set_layout()
当然,现在将该方法称为像handle_url()
这样的方法会更合适。
但还有一个问题。 我猜是由于某些缓存问题,除非用户再次手动调用刷新,否则 dash 会显示该图的旧版本。 解决方案是添加一个dcc.Interval
回调来更新图形(它调用draw_map()
)。 不需要每秒都执行此操作,例如,您可以将间隔设置为 999999。 事实上,您可以设置 'max_intervals=1' 并且它仍然有效。 只要有回调,就会在页面第一次刷新时调用,并更新图形。
事实上,有了这个, draw_map()
和set_layout()
函数甚至不需要在 url 回调中调用。 所以代码现在看起来像这样:
def shutdown(self, pathname):
if pathname == '/shutdown':
print("Trying to shutdown map dash")
func = request.environ.get('werkzeug.server.shutdown')
if func is None:
raise RuntimeError('Not running with the Werkzeug Server')
func()
self.shutting_down.emit(True)
elif pathname == '/refresh':
print("I'm doing nothing")
它有效。 但是 url 回调本身是必要的。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.