PyQt学习(14):多线程(Qtimer和Qthread),网页交互(QWebEngineView,js与python的交互)

本文介绍了PyQt中多线程的使用,包括QTimer和QThread,详细讲解了如何通过QWebEngineView进行网页交互,特别是QWebChannel实现JavaScript与Python的双向通信,用于执行更复杂的任务和信息传递。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

1,QTimer

2,QThread

3,网页交互(QWebEngineView和QWebChannel)

3.1显示网页页面

3.2,JS与Pyqt交互(QwebEngineView 和QwebChannel)

3.2.1,PyQt调用js (runJavaSCript)

3.2.2 js调用python程序(QwebChannel)比较复杂。其实是实现了js与python程序调用

3.2.3 runjavaScript和 Qwebchannel传递信息的不同用处


 

多线程技术涉及三种方法,其中一个是使用计时器模块QTimer,一种是使用多线程模块QThread,还有一种是使用事件处理的功能

1,QTimer

如果在应用程序中周期性地进行某项操作,使用QTimer,Qtimer类提供了重复的和单次的定时器要是用定时器,需要先创建一个QTimer实例,将其timeout信号连接到相应的槽,并调用start()。然后,定时器以恒定的间隔发出timeout信号

Qtimer类中常用方法:

start(millseconds):启动或重新自动定时器,时间间隔为毫秒,如果定时已经运行,它将被停止并重新启动,如果sigleShot信号为真,定时器将被激活一次

stop()停止计时器

常用的信号:

signalShot:在给定的时间间隔调用一个槽函数时发射此信号

timeout:当定时器超时时发射此信号

注意:start()之后,每秒都会调用update()

from PyQt5.QtWidgets import QGridLayout,QApplication,QWidget,QPushButton,QLabel
import sys
from PyQt5.QtCore import QTimer,QDateTime

class TimerDemo(QWidget):
	def __init__(self):
		super(TimerDemo, self).__init__()
		self.initUI()
	def initUI(self):
		self.setWindowTitle('demo')
		self.setGeometry(300,300,300,200)
		layout=QGridLayout(self)
		#定义计时器,并连接槽函数
		self.timer=QTimer()
		self.timer.timeout.connect(self.show_time)

		self.label=QLabel()
		layout.addWidget(self.label,0,0,1,2)

		self.btn1=QPushButton('start',self)
		layout.addWidget(self.btn1,1,0)
		self.btn1.clicked.connect(self.start_program)

		self.btn2=QPushButton('end',self)
		layout.addWidget(self.btn2,1,1)
		self.btn2.clicked.connect(self.end_program)

	def show_time(self):
		date=QDateTime.currentDateTime()
		now=date.toString('yyyy-MM--dd hh:mm:ss')
		self.label.setText(now)

	def start_program(self):
		self.timer.start(1000)
		self.btn1.setEnabled(False)
		self.btn2.setEnabled(True)
	def end_program(self):
		self.timer.stop()
		self.btn2.setEnabled(False)
		self.btn1.setEnabled(True)

if __name__=='__main__':
	app=QApplication(sys.argv)
	demo=TimerDemo()
	demo.show()
	sys.exit(app.exec_())
from PyQt5.QtWidgets import QLabel,QApplication
import sys
from PyQt5.QtCore import QTimer,Qt


if __name__ == '__main__':
	app=QApplication(sys.argv)
	label=QLabel('五秒退出')
	label.setWindowFlags(Qt.SplashScreen|Qt.FramelessWindowHint)
	label.show()
	#设置窗口无边框
	
	timer=QTimer()
	timer.singleShot(5000,app.quit)#*********
	sys.exit(app.exec_())

2,QThread

QThread是Qt线程类中最核心的底层类,由于PyQt的跨平台特性,QThread隐藏所有与平台相关的代码

要使用QThread开始一个线程,可以创建它的一个子类,然后覆盖其QThread.run()函数

QThread类中常用的方法和信号:

start():启动线程

wait():阻止线程,知道满足如下条件之一,*与此QThread对象关联的线程已完成执行,即从run()返回时。如果线程

完成执行,此函数将返回True:如果线程尚未启动,此函数也返回True。*等待时间的单位是毫秒,如果事件是ULONG_MAX(默认值)。则等待,永远不会超时。如果等待超时,此函数将返回False

QThread类中常用的信号

started:在开始执行run()函数之前,从相关线程发射此信号

finished:当线程完成业务逻辑时,从相关线程发射此信号

流程:

class Thread(QThread):     #继承QThread
      def __init__(self):
            super(Thread,self).__init__()
      def run(self):
      #线程相关的代码
      passs
========================
#创建一个新的线程
thread=Thread()
thread.start()

import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

global sec
sec = 0


class WorkThread(QThread):
    #实例化一个信号对象
    trigger = pyqtSignal()

    def __int__(self):
        super(WorkThread, self).__init__()

    def run(self):
        #开始进行循环
        for i in range(2000000000):
            pass

        # 循环完毕后发出信号
        self.trigger.emit()


def countTime():
    global sec
    sec += 1
    # LED显示数字+1
    lcdNumber.display(sec)


def work():
    # 计时器每秒计数
    timer.start(1000)
    # 计时开始
    workThread.start()
    # 当获得循环完毕的信号时,停止计数
    workThread.trigger.connect(timeStop)


def timeStop():
    #定时器停止
    timer.stop()
    print("运行结束用时", lcdNumber.value())
    global sec
    sec = 0


if __name__ == "__main__":
    app = QApplication(sys.argv)
    top = QWidget()
    top.resize(300, 120)

    # 垂直布局类QVBoxLayout
    layout = QVBoxLayout(top)

    #
<think>我们正在讨论如何使用PyQt5的moveToThread方法来实现多线程数据库查询,以避免阻塞主线程(即GUI线程)。根据引用[1],官方现在推荐使用moveToThread而不是继承QThread。 核心步骤: 1. 创建一个工作类(继承自QObject),其中包含执行数据库查询的方法。 2. 创建一个QThread对象。 3. 使用moveToThread将工作对象移动到新线程。 4. 通过信号触发工作对象的方法(注意:只能通过信号槽调用工作对象的槽函数,不能直接调用)。 5. 在工作对象中,通过信号将结果或状态传递回主线程。 6. 注意线程结束时的清理工作。 我们将详细说明如何实现,并提供一个完整的示例。 注意:在多线程中操作数据库需要确保数据库连接是在目标线程中创建的(因为有些数据库连接不是线程安全的,比如SQLite要求每个线程使用自己的连接)。 下面是一个使用SQLite数据库的示例,我们将展示如何安全地进行查询。 步骤详解: 1. 定义工作类(DatabaseWorker): - 它应该包含一个执行查询的槽(例如execute_query)。 - 定义信号用于传递结果、错误进度。 2. 在主线程中创建界面,并创建线程工作者。 3. 连接信号: - 将线程的started信号连接到工作者的执行槽(注意:不能直接调用工作者的方法,因为工作者已经移动到另一个线程)。 - 将工作者的结果信号连接到主界面的更新槽(这些槽将在主线程中执行,因为信号槽连接默认是Qt::AutoConnection,当信号跨线程时,会以队列方式在主线程的事件循环中执行)。 4. 启动线程:调用thread.start(),这将在线程中启动事件循环,并触发started信号,从而调用工作者的槽。 5. 清理:当工作完成后,工作者发出完成信号,连接该信号到线程的quit槽,然后连接线程的finished信号到工作者线程的deleteLater,以确保资源释放。 6. 注意:在工作者对象中,不能创建任何GUI对象,也不能直接操作界面。所有界面的交互都通过信号。 下面是一个完整的代码示例: 注意:我们使用SQLite数据库,并假设有一个名为"example.db"的数据库,其中有一张表"large_table"(仅作示例)。</think># PyQt5 多线程操作:使用 moveToThread 实现数据库查询详解 ## 一、moveToThread 机制核心原理 `moveToThread` 是 Qt 框架中实现多线程的核心方法,它允许将一个 `QObject` 及其子对象迁移到新线程中执行。这种方法传统的继承 `QThread` 方式相比,具有更好的**对象隔离性****线程安全性**。 ### 工作流程: ```mermaid graph LR A[主线程] -->|发送查询请求信号| B[工作对象] B -->|moveToThread| C[工作线程] C -->|执行数据库操作| D[数据库] D -->|返回结果信号| A ``` ### 关键优势: 1. **对象线程亲性**:每个对象只能属于一个线程 2. **信号槽自动跨线程**:Qt 自动处理线程间通信 3. **资源隔离**:数据库连接等资源独立于主线程 4. **事件驱动**:通过事件循环实现异步处理 ## 二、完整实现方案 ### 1. 工作对象定义(DatabaseWorker) ```python from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot class DatabaseWorker(QObject): # 定义信号 resultReady = pyqtSignal(list) # 查询结果信号 progressUpdated = pyqtSignal(int) # 进度更新信号 errorOccurred = pyqtSignal(str) # 错误信号 finished = pyqtSignal() # 完成信号 def __init__(self, db_path): super().__init__() self.db_path = db_path self.abort_requested = False @pyqtSlot(str) def execute_query(self, query): """执行数据库查询的槽函数""" import sqlite3 try: self.abort_requested = False conn = sqlite3.connect(self.db_path) cursor = conn.cursor() cursor.execute(query) # 分块获取结果,避免大结果集阻塞 results = [] chunk_size = 100 total_rows = 0 while True: if self.abort_requested: break chunk = cursor.fetchmany(chunk_size) if not chunk: break results.extend(chunk) total_rows += len(chunk) self.progressUpdated.emit(total_rows) conn.close() self.resultReady.emit(results) except Exception as e: self.errorOccurred.emit(f"数据库错误: {str(e)}") finally: self.finished.emit() @pyqtSlot() def abort(self): """中止当前查询""" self.abort_requested = True ``` ### 2. 线程管理界面整合 ```python from PyQt5.QtWidgets import (QApplication, QMainWindow, QPushButton, QTextEdit, QVBoxLayout, QWidget, QProgressBar) from PyQt5.QtCore import QThread class DatabaseWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("数据库查询工具") self.resize(600, 400) # 创建界面组件 self.query_input = QTextEdit() self.query_input.setPlaceholderText("输入SQL查询语句...") self.result_output = QTextEdit() self.result_output.setReadOnly(True) self.btn_execute = QPushButton("执行查询") self.btn_stop = QPushButton("停止查询") self.btn_stop.setEnabled(False) self.progress_bar = QProgressBar() self.progress_bar.setRange(0, 100) # 布局 layout = QVBoxLayout() layout.addWidget(self.query_input) layout.addWidget(self.btn_execute) layout.addWidget(self.btn_stop) layout.addWidget(self.progress_bar) layout.addWidget(self.result_output) container = QWidget() container.setLayout(layout) self.setCentralWidget(container) # 连接信号 self.btn_execute.clicked.connect(self.start_query) self.btn_stop.clicked.connect(self.stop_query) # 线程相关变量 self.worker = None self.thread = None def start_query(self): """启动查询线程""" query = self.query_input.toPlainText() if not query: return # 创建线程工作对象 self.thread = QThread() self.worker = DatabaseWorker('example.db') # 将工作对象移动到新线程 self.worker.moveToThread(self.thread) # 连接信号 self.thread.started.connect(lambda: self.worker.execute_query(query)) self.worker.resultReady.connect(self.handle_results) self.worker.progressUpdated.connect(self.update_progress) self.worker.errorOccurred.connect(self.handle_error) self.worker.finished.connect(self.cleanup_thread) # 启动线程 self.thread.start() # 更新界面状态 self.btn_execute.setEnabled(False) self.btn_stop.setEnabled(True) self.result_output.clear() self.result_output.append("开始执行查询...") def stop_query(self): """停止当前查询""" if self.worker: self.worker.abort() self.result_output.append("正在中止查询...") def handle_results(self, results): """处理查询结果""" self.result_output.append(f"查询完成,共返回 {len(results)} 条记录") # 实际应用中可根据需要处理结果 def update_progress(self, row_count): """更新进度条""" self.progress_bar.setValue(row_count % 100) # 示例进度更新 def handle_error(self, error_msg): """处理错误""" self.result_output.append(f"错误: {error_msg}") def cleanup_thread(self): """清理线程资源""" if self.thread: self.thread.quit() self.thread.wait(1000) # 等待线程结束 self.thread.deleteLater() self.thread = None self.worker = None self.btn_execute.setEnabled(True) self.btn_stop.setEnabled(False) self.result_output.append("查询结束") if __name__ == "__main__": app = QApplication(sys.argv) window = DatabaseWindow() window.show() sys.exit(app.exec_()) ``` ## 三、关键技术点解析 ### 1. 线程生命周期管理 ```mermaid sequenceDiagram participant UI as 主界面 participant Thread as 工作线程 participant Worker as 工作对象 UI->>Thread: 创建线程 UI->>Worker: 创建工作对象 UI->>Worker: moveToThread(线程) UI->>Thread: start() Thread->>Worker: 触发execute_query槽 Worker->>UI: 发送progressUpdated信号 Worker->>UI: 发送resultReady信号 Worker->>Thread: 发送finished信号 Thread->>UI: 发送finished信号 UI->>Thread: quit() + deleteLater() ``` ### 2. 信号槽连接规范 在跨线程通信中,必须遵循以下规则: 1. 工作对象的所有槽函数必须使用 `@pyqtSlot` 装饰器 2. 工作对象不能直接调用主线程的GUI方法 3. 所有界面更新必须通过信号触发 4. 工作对象中避免创建任何GUI组件 ### 3. 数据库连接安全 在多线程环境下操作数据库需要特别注意: ```python # 每个线程创建独立的数据库连接 conn = sqlite3.connect(self.db_path) # SQLite 需要设置线程模式 conn.execute("PRAGMA journal_mode=WAL;") # 写入日志模式 # 使用后正确关闭连接 conn.close() ``` ## 四、高级优化技巧 ### 1. 线程池管理 对于频繁查询场景,使用 `QThreadPool` 管理线程: ```python from PyQt5.QtCore import QRunnable, QThreadPool class QueryTask(QRunnable): def __init__(self, query, db_path): super().__init__() self.query = query self.db_path = db_path self.signals = WorkerSignals() # 自定义信号类 def run(self): # 执行查询并发送信号 # ... # 使用线程池 thread_pool = QThreadPool.globalInstance() task = QueryTask("SELECT * FROM table", "example.db") thread_pool.start(task) ``` ### 2. 查询超时控制 ```python import time def execute_query(self, query): start_time = time.time() TIMEOUT = 30 # 30秒超时 while not self.abort_requested: # 执行查询分片... if time.time() - start_time > TIMEOUT: self.errorOccurred.emit("查询超时") break ``` ### 3. 结果分页处理 对于大型结果集,使用服务器端分页: ```python def execute_query(self, query): page_size = 500 offset = 0 while not self.abort_requested: paged_query = f"{query} LIMIT {page_size} OFFSET {offset}" cursor.execute(paged_query) results = cursor.fetchall() if not results: break self.resultChunkReady.emit(results) # 发送分块结果 offset += page_size ``` ## 五、常见问题解决方案 ### 1. 对象生命周期管理 正确销毁对象链: ```python # 连接信号确保资源释放 worker.finished.connect(thread.quit) worker.finished.connect(worker.deleteLater) thread.finished.connect(thread.deleteLater) ``` ### 2. 数据库锁定问题 使用重试机制处理数据库锁: ```python MAX_RETRIES = 3 RETRY_DELAY = 0.1 # 秒 for attempt in range(MAX_RETRIES): try: cursor.execute(query) break except sqlite3.OperationalError as e: if "locked" in str(e) and attempt < MAX_RETRIES - 1: time.sleep(RETRY_DELAY * (2 ** attempt)) # 指数退避 else: raise ``` ### 3. 界面卡顿优化 限制进度更新频率: ```python from PyQt5.QtCore import QTimer # 在界面类中 def __init__(self): self.progress_timer = QTimer() self.progress_timer.setInterval(200) # 200ms更新一次 self.progress_timer.timeout.connect(self.update_progress_bar) self.current_progress = 0 def handle_progress(self, value): """缓冲进度值""" self.current_progress = value if not self.progress_timer.isActive(): self.progress_timer.start() ``` ## 六、最佳实践总结 1. **单一职责原则**:工作对象只负责数据处理,不涉及UI 2. **信号驱动**:所有跨线程通信通过信号槽实现 3. **资源隔离**:每个线程使用独立的数据库连接 4. **优雅中止**:实现可中断的查询算法 5. **进度反馈**:提供实时进度更新 6. **错误处理**:完善的异常捕获错误报告机制 通过 `moveToThread` 实现的多线程数据库查询,可以有效避免界面冻结,提升用户体验,同时保持代码的可维护性扩展性[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值