You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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更新,完全规避跨线程操作风险:

  1. 自定义信号类继承QObject,定义接收快照数据的信号
  2. 子线程中仅做sleep和REST请求,拿到数据后发射信号
  3. 主线程绑定信号处理函数,做数据合并和GUI更新操作

内容的提问来源于stack exchange,提问作者user1884295

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.24 07:06:04