Python子线程调用主线程函数跨线程更新PyQtGraph报错求助
问题根源
- Qt规定所有GUI组件的属性修改、绘制操作只能在主线程执行,你在子线程中调用的
getOrderBookSnapshot最终会触发BarGraphItem的属性修改,直接触发跨线程操作报错,和对象传引用/拷贝无关,本质是操作线程不符合Qt的GUI线程约束。 - 你不需要单独开子线程实现非阻塞等待,Qt原生
QTimer可以在不阻塞主线程事件循环的前提下实现延迟调用,完美适配你的需求。
解决方案1:使用QTimer替代子线程(最简洁,无跨线程风险)
仅需修改少量代码即可运行:
import json import importlib from requests.api import get import functools import pyqtgraph as pg from pyqtgraph.Qt import QtCore QtWebSockets = importlib.import_module(pg.Qt.QT_LIB + '.QtWebSockets') class coin(): def __init__(self): self.orderBook = {'bids':{}, 'asks':{}} self.SnapShotRecieved = False self.last_uID = 0 self.ordBookBuff = [] self.pltwgt = pg.PlotWidget() self.pltwgt.show() self.bidBar = pg.BarGraphItem(x=[0], height=[1], width= 1, brush=(25,25,255,125), pen=(0,0,0,0)) self.askBar = pg.BarGraphItem(x=[1], height=[1], width= 1, brush=(255,25,25,125), pen=(0,0,0,0)) self.pltwgt.addItem(self.bidBar) self.pltwgt.addItem(self.askBar) def updateOrderBook(self, message): for side in ['a','b']: bookSide = 'bids' if side == 'b' else 'asks' for update in message[side]: if float(update[1]) == 0: try: del self.orderBook[bookSide][float(update[0])] except: pass else: self.orderBook[bookSide].update({float(update[0]): float(update[1])}) while len(self.orderBook[bookSide]) > 1000: del self.orderBook[bookSide][(min(self.orderBook['bids'], key=self.orderBook['bids'].get)) if side == 'b' else (max(self.orderBook['asks'], key=self.orderBook['asks'].get))] if self.SnapShotRecieved == True: self.bidBar.setOpts(x0=list(self.orderBook['bids'].keys()), height=list(self.orderBook['bids'].values()), width=1 ) self.askBar.setOpts(x0=list(self.orderBook['asks'].keys()), height=list(self.orderBook['asks'].values()), width=1 ) def getOrderBookSnapshot(self): orderBookEncoded = get('https://api.binance.com/api/v3/depth?symbol=BTCUSDT&limit=1000') if orderBookEncoded.ok: rawOrderBook = orderBookEncoded.json() orderBook = {'bids':{}, 'asks':{}} for orders in rawOrderBook['bids']: orderBook['bids'].update({float(orders[0]): float(orders[1])}) for orders in rawOrderBook['asks']: orderBook['asks'].update({float(orders[0]): float(orders[1])}) last_uID = rawOrderBook['lastUpdateId'] while self.ordBookBuff and self.ordBookBuff[0]['u'] <= last_uID: del self.ordBookBuff[0] self.orderBook = orderBook self.last_uID = last_uID if len(self.ordBookBuff) >= 1 : for eachUpdate in self.ordBookBuff: self.last_uID = eachUpdate['u'] self.updateOrderBook(eachUpdate) self.ordBookBuff = [] self.SnapShotRecieved = True else: print('Error retieving order book.') #RESTfull request failed def on_text_message(message, refObj): messaged = json.loads(message) if refObj.SnapShotRecieved == False: refObj.ordBookBuff.append(messaged) else: refObj.updateOrderBook(messaged) def main(): pg.mkQApp() refObj = coin() websock = QtWebSockets.QWebSocket() websock.connected.connect(lambda : print('connected')) websock.disconnected.connect(lambda : print('disconnected')) websock.error.connect(lambda e : print('error', e)) websock.textMessageReceived.connect(functools.partial(on_text_message, refObj=refObj)) url = QtCore.QUrl("wss://stream.binance.com:9443/ws/btcusdt@depth@1000ms") websock.open(url) # 替换原有子线程逻辑,2000毫秒后自动在主线程调用getOrderBookSnapshot QtCore.QTimer.singleShot(2000, refObj.getOrderBookSnapshot) pg.exec() if __name__ == "__main__": main()
注:额外修复了原代码的两处bug:setOpts的x0和height参数需要传入列表而非dict_keys对象,否则会报错;同时增加了缓存为空的判断避免索引越界。
解决方案2:保留子线程的优化方案
如果REST请求耗时较高,怕阻塞主线程,可以将网络请求放在子线程执行,子线程仅负责拉取数据,拿到数据后通过Qt信号槽通知主线程做合并和GUI更新,完全规避跨线程操作风险:
- 自定义信号类继承QObject,定义接收快照数据的信号
- 子线程中仅做sleep和REST请求,拿到数据后发射信号
- 主线程绑定信号处理函数,做数据合并和GUI更新操作
内容的提问来源于stack exchange,提问作者user1884295
相关产品推荐
相关产品推荐

