プログラムを中心とした個人的なメモ用のブログです。 タイトルは迷走中。
内容の保証はできませんのであしからずご了承ください。

2024/04/22

[PySide] qasync を使って非同期処理をバックグランドで行う

event_note2024/04/22 0:51

PySide6 で GUI アプリを作成している中で、I/O バウンドな処理の完了を待っていると GUI が固まりました。

通常、バックグラウンドで何か処理を行う際に GUI の処理を止めないようにするために、QThread などを使って別スレッドで処理を行いつつ GUI スレッドを回したりします。
今回もそうやって作っていたのですが、完了までに時間のかかる I/O バウンドな処理を行わせたい場合、上手くいかない場合がありました。 I/O バウンドな処理なので、ayncio を使って非同期処理にしてやればいいと思ったのですが、Qt のイベントループと asyncio を適切に統合する必要があるようで、どのように記述すればいいのかいまいち分かりませんでした。

そんな中、qasync というライブラリを使えば、とても簡単に処理を組み込むことができました。

コードの記述も、QThread を使う場合に比べシンプルでスッキリします。

ちなみに、asyncqt というライブラリもありますが、こちらは既にメンテナンスされていないようです。 qasyncasyncqt のフォークで、現在でもメンテナンスされているようなので、使うなら qasync のほうが良いと思います。

環境

  • python 3.11
  • pyside6 6.6.3.1
  • qasync 0.27.1

サンプルコード

import sys
import asyncio
from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton, QVBoxLayout, QWidget, QLabel
from qasync import QEventLoop, asyncSlot

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("qasync sample")

        self.label = QLabel("Press the button.", self)
        self.button = QPushButton("Start", self)
        self.button.clicked.connect(self.on_button_clicked)

        layout = QVBoxLayout()
        layout.addWidget(self.label)
        layout.addWidget(self.button)

        container = QWidget()
        container.setLayout(layout)
        self.setCentralWidget(container)

    @asyncSlot()
    async def on_button_clicked(self):
        self.label.setText("waiting...")
        self.button.setEnabled(False)

        # I/O バウンドな処理を想定
        await asyncio.sleep(5)

        self.label.setText("Press the button.")
        self.button.setEnabled(True)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    loop = QEventLoop(app)
    asyncio.set_event_loop(loop)
    window = MainWindow()
    window.show()
    with loop:
        loop.run_forever()