python - 如何在另一个线程中设置 qasync 事件循环?

标签 python multithreading pyqt pyqt5 python-asyncio

我正在开发一个异步 PyQt 应用程序 - 也就是说,我需要 asyncio 和 PyQt。由于 asyncio 和 PyQt 都使用它们自己的事件循环,如果没有 qasync,这是不可能的,它是为解决这个问题而创建的。以下是您如何使用它:

app = QApplication(sys.argv)
asyncio.set_event_loop(qasync.QEventLoop(app))
exit(app.exec_())

现在,我想将我所有的逻辑移动到一个单独的“工作”线程中。为此,我创建了一个 QThread,它会自动为此线程设置一个新的 Qt 事件循环。但我也需要能够在该线程中使用 asyncio,因此我必须对工作线程执行相同的 qasync 技巧。

但是我该怎么做呢?据我了解,qasync.QEventLoop(app) 将获得应用程序的主事件循环,而不是线程的循环。你不能做 qasync.QEventLoop(thread)

这是一个代码示例。

import logging
import sys
import datetime
import asyncio
import qasync

from PyQt5.QtCore import QObject, pyqtSlot, QThread
from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QWidget, QVBoxLayout


class Logic(QObject):  # Logic to run in a separate thread
    enabled = False

    @pyqtSlot()
    def start(self):
        logger.debug('Start called')
        self.enabled = True
        asyncio.create_task(self.process())

    @pyqtSlot()
    def stop(self):
        logger.debug('Stop called')
        self.enabled = False

    async def process(self):
        while self.enabled:
            logger.debug(f'Processing ({datetime.datetime.now()})...')
            await asyncio.sleep(0.5)


if __name__ == '__main__':
    logging.basicConfig(format='%(levelname)s:%(name)s: %(message)s')
    logger = logging.getLogger(__name__)
    logger.setLevel(logging.DEBUG)

    app = QApplication(sys.argv)
    asyncio.set_event_loop(qasync.QEventLoop(app))

    logic = Logic()

    # Move logic to another thread
    # thread = QThread()
    # logic.moveToThread(thread)
    # thread.start()

    window = QMainWindow()
    window.setCentralWidget(QWidget())
    window.centralWidget().setLayout(QVBoxLayout())

    window.centralWidget().layout().addWidget(QPushButton(text='Start', clicked=logic.start))
    window.centralWidget().layout().addWidget(QPushButton(text='Stop', clicked=logic.stop))

    window.show()

    logger.debug('Launching the application...')
    exit(app.exec_())

如果按原样运行它,它会在单个线程中运行。但是如果你取消注释“Move logic to another thread” block ,那么它就会失败,因为 RuntimeError: no running event loop for asyncio.create_task()

我该如何解决这个问题?我想所需的行为很明显 - 它的行为应该与在单个线程中的行为相同,除了 logic 中的所有内容都应该在单独的线程中运行。

一个子问题是,qasync 究竟做了什么?我的理解是否正确,它以某种方式允许对 asyncio 使用相同的主 Qt 事件循环?所以 qasync.QEventLoop(app) 返回一个...某种 asyncio 兼容的代理对象到现有的 Qt 事件循环?..

最佳答案

我认为问题是由于您正在设置从 QApplication(在主线程中)创建的 QEventLoop。这是我的版本,用于展示如何从单独的线程运行 asyncio:

import logging
import sys
import datetime
import asyncio
import qasync
import threading
from PyQt5.QtCore import QObject, pyqtSlot, QThread, pyqtSignal
from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QWidget, QVBoxLayout


class Logic(QObject):  # Logic to run in a separate thread
    enabled = False

    @pyqtSlot()
    def start(self):
        logger.debug('Start called')
        self.enabled = True
        asyncio.create_task(self.process())

    @pyqtSlot()
    def stop(self):
        logger.debug('Stop called')
        self.enabled = False

    async def process(self):
        while self.enabled:
            # demonstrate that we can emit a signal from a thread
            self.message.emit(f'Processing ({datetime.datetime.now()}), worker thread id: {threading.get_ident()}...')
            await asyncio.sleep(0.5)

    # signal declaration
    message = pyqtSignal(str)

# subclass QThread. override the run function to create an event loop and run forever
class WorkerThread(QThread):
    def run(self):
        loop = qasync.QEventLoop(self)
        asyncio.set_event_loop(loop)
        loop.run_forever()

if __name__ == '__main__':
    logging.basicConfig(format='%(levelname)s:%(name)s: %(message)s')
    logger = logging.getLogger(__name__)
    logger.setLevel(logging.DEBUG)

    app = QApplication(sys.argv)
    logger.debug(f'main thread id: {threading.get_ident()}')

    # Move logic to another thread
    logic = Logic()    
    thread = WorkerThread()
    logic.moveToThread(thread)
    thread.start()

    window = QMainWindow()
    window.setCentralWidget(QWidget())
    window.centralWidget().setLayout(QVBoxLayout())

    window.centralWidget().layout().addWidget(QPushButton(text='Start', clicked=logic.start))
    window.centralWidget().layout().addWidget(QPushButton(text='Stop', clicked=logic.stop))
    # connect logic in workerThread to lambda function in this thread
    logic.message.connect(lambda msg: logger.debug(f'current thread: {threading.get_ident()}, {msg}'))

    window.show()

    logger.debug('Launching the application...')
    exit(app.exec_())

关于python - 如何在另一个线程中设置 qasync 事件循环?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67979231/

相关文章:

python - 尝试在 ubuntu 上为 django 设置 mysql 但出现错误 'django.db.utils.OperationalError: (1046, ' 未选择数据库')'

python - django模型不创建postgresql表

java - 如何在另一个线程中重新抛出异常以捕获 block

Java 信号量选项

python - 如何使 QIcon 可点击?

python - 自定义 Django 命令的自定义位置

python - 以相同顺序打印列表的最大值而不创建新列表

java - 分析Java线程转储

python PyQt : Is it possible to use QThread with a non-GUI program?

c++ - 我可以更改 glui 面板背景颜色吗?