質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Qt

QtはGUIプログラムの開発で広く使われているクロスプラットフォーム開発のフレームワークです。

PyCharm

エディター・開発ツール

Q&A

解決済

1回答

3552閲覧

PyQt5で描画を止めないロード画面を実現したい。

sponge_yukari

総合スコア12

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Qt

QtはGUIプログラムの開発で広く使われているクロスプラットフォーム開発のフレームワークです。

PyCharm

エディター・開発ツール

0グッド

0クリップ

投稿2019/01/10 08:40

こんばんわ
仕様が無いのかよくわからないので質問します

やりたいこと

ロード画面を実現したいので、QThreadを使って実現しようとしました。
しかし、QThreadに重い処理を入れて稼動させるとMainThreadの動作(描画)が止まってしまい、実現しませんでした。
この問題を解決したいです。

環境

Python3.6
PyQt5
PyCharm

ソース

Python

1import sys 2from PyQt5.QtWidgets import QApplication, QProgressDialog 3from PyQt5.QtCore import QObject, QThread, pyqtSignal, pyqtSlot 4from PyQt5 import QtWidgets 5 6"""ロード用のスレッド""" 7class Worker(QObject): 8 once = pyqtSignal() 9 loop = pyqtSignal() 10 finished = pyqtSignal() 11 12 def __init__(self, name): 13 super(Worker, self).__init__() 14 self.name = name 15 self.end = False 16 17 def run(self): 18 print("[start]" + self.name) 19 self.once.emit() 20 while not self.end: 21 self.loop.emit() 22 if self.end: 23 break 24 QThread.sleep(2) 25 self.finished.emit() 26 print("[end]" + self.name) 27 28 @pyqtSlot() 29 def finish(self): 30 self.end = True 31 32 33"""ロード画面""" 34class Loading(QProgressDialog): 35 36 def __init__(self): 37 super(Loading, self).__init__() 38 self.setRange(0,0) 39 40 # 処理用 41 self.processth = QThread() 42 self.processworker = Worker("worker") 43 self.processworker.moveToThread(self.processth) 44 self.processworker.once.connect(self.loadProcess) 45 self.processworker.finished.connect(self.processworker.deleteLater) 46 self.processworker.finished.connect(self.endProcess) 47 self.processth.started.connect(self.processworker.run) 48 49 50 51 """ロード中に行う処理""" 52 def loadProcess(self): 53 #本来はこの部分に重い処理を入れます 54 QThread.sleep(5) #5秒待機 55 print("待機後") 56 57 58 """スレッドを開始します。""" 59 def startProcess(self): 60 self.processth.start() 61 62 def endProcess(self): 63 print("スレッドが終了しました。") 64 65if __name__ == "__main__": 66 print("start") 67 app = QApplication([]) 68 l = Loading() 69 l.setGeometry(300, 300, 100, 50) 70 l.show() 71 l.startProcess() 72 sys.exit(app.exec_())

※finishedまでたどり着かないのは気にしないでください。

実装方法はQObjectを継承したworkerを使って、moveToThreadでスレッドを入れるやり方です。
仮のプログレスバー画面を表示してから擬似的な重い処理として5秒のスリープを行うスレッドを稼動させています。

試したこと

「実はMainThread上で動いており、それが影響して描画が止まるのでは?」
そう思ったので、スレッド1の上にスレッド2を乗せて、スレッド1を常時更新できるようにしましたが、それでも描画が止まりました。

QApplication.processEventsも設置してみましたが、だめでした。
そもそもループ中ではなく、1つの重い処理を想定しているため何度も呼び出せるものではありません。

その他

Qthreadでは実現不可能なのでは?と少し疑っていたりします。
方法自体が間違ってるならそれでいいんですが・・・。

以上です、よろしくお願いします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

自分はQtは最近真剣にやり始めたばかりで、かつすぐに試せる環境はないので、以下の回答を参考に自身で検証してください。
(前から自分も興味があった話なので、調べてみました)

Qt::AutoConnection

受信側が同じスレッド上にいる場合は DirectoConnection となり、それ以外の場合は、 QueuedConnection となる。
Qt::QueuedConnection
スロットのイベントループ呼び出し。
シグナルは受信側のスレッドのキューに入れられて、受信側スレッドのイベントループによりスロットが呼び出されます。
シグナル発呼側は、レシーバー側のキューに入れて即時処理を再開します(スロットの実行を待たない)

上記を元に参考にし、質問者さんのソースコードを見直すと、onceのシグナルのemitはワーカースレッドで行われますが、onceのシグナルにconnectされているLoading.loadProcessメソッドは受信側スレッドのイベントループ、つまり メインスレッドにて実行される と思います。

Loading.loadProcessメソッドをワーカースレッドで実行するには、Qt::DirectConnectionにしなければなりません。

上記の参考に、

python

1 #self.processworker.once.connect(self.loadProcess) 2 self.processworker.once.connect(self.loadProcess, type=Qt.DirectConnection)

とすると、ワーカースレッド側で処理されると思います。
もちろん、loadProcessメソッドとその他のメソッドでは別スレッドで処理されることになるので、スレッド間の同期処理も考慮する必要がありますので、ご注意。

(余談:こう見ると、シグナルのスレッド処理はGTK+よりQtの方が優秀だなぁ)


検証してみました。
(linuxmint19.1/python3.6.7、PyQt5でなくPySide2です。)

python

1# coding: utf-8 2 3import sys, threading 4from PySide2.QtWidgets import * 5from PySide2.QtCore import * 6from PySide2.QtGui import * 7 8def debugOutput(s): 9 sys.stdout.write(s + '\n') 10 sys.stdout.flush() 11 12class Notifier(QObject): 13 notify = Signal() 14 15class Thread(QThread): 16 def __init__(self, notifier): 17 super().__init__() 18 self.notifier = notifier 19 20 def run(self): 21 debugOutput('start thread %#x.' % threading.get_ident()) 22 self.notifier.notify.emit() 23 24class MainWindow(QWidget): 25 def __init__(self): 26 super().__init__() 27 28 layout = QVBoxLayout() 29 self.setLayout(layout) 30 31 button = QPushButton('Start') 32 button.clicked.connect(self.onClicked) 33 layout.addWidget(button) 34 35 def onClicked(self): 36 self.notifier = Notifier() 37 self.thread = Thread(self.notifier) 38 self.notifier.moveToThread(self.thread) 39 #self.notifier.notify.connect(self.onNotify) 40 self.notifier.notify.connect(self.onNotify, type=Qt.DirectConnection) 41 self.thread.start() 42 43 def onNotify(self): 44 debugOutput('received notify signal %#x.' % threading.get_ident()) 45 46if __name__ == '__main__': 47 debugOutput('main thread %#x.' % threading.get_ident()) 48 49 app = QApplication(sys.argv) 50 51 window = MainWindow() 52 window.show() 53 54 sys.exit(app.exec_())

Qt.DirectConnectionを指定しない場合。

main thread 0x7f2f8fd6f740. start thread 0x7f2f67f02700. received notify signal 0x7f2f8fd6f740.

Qt.DirectConnectionを指定した場合。

main thread 0x7ff207af9740. start thread 0x7ff1dfbea700. received notify signal 0x7ff1dfbea700.

投稿2019/01/10 12:49

編集2019/01/10 14:05
katsuko

総合スコア3469

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

sponge_yukari

2019/01/31 05:19

回答ありがとうございます。 これとはまた別のスレッド処理で試していたら色々うまくいかず返事が遅くなりました。 まさかその1文を入れないと全く意味のないスレッドとなるとは思いませんでした。 なかなか不可解な動きをすることが多くて混乱するばかりです。 他の言語のスレッドとは違う感じで、難しいですね。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問