テンプレートテスト
ブログの説明
ブログの説明2
menu
keyboard_arrow_up
Top
search
close
home
ホーム
computer
PC一般
construction
開発環境・ツール
code
プログラミング
home
ホーム
computer
PC一般
construction
開発環境・ツール
code
プログラミング
Home
›
Unlabelled
›
2022/02/20
update
event_note
例えばボタンをクリックしたときに重い処理を実行する場合など、そのまま処理すると GUI が固まってしまうため、スレッド化してやります。
## 概要 python の標準モジュールに `threading` がありますが、PySide を使っている場合は `QThread` を使います。 本質的にはどちらも同じだそうですが、Signal/Slot など、Qt との対話を行うなら `QThread` を使ったほうが良いようです。 - https://stackoverflow.com/questions/1595649/threading-in-a-pyqt-application-use-qt-threads-or-python-threads ちなみに、Qt には `QtConcurrent` という `QThread` よりも高レベルな API がありますが、こちらは PySide では提供されていないようです。 - https://stackoverflow.com/questions/32378719/qtconcurrent-in-pyside-pyqt - https://doc.qt.io/qtforpython/overviews/qtconcurrentrun.html `QThread` のドキュメントは以下です。 - https://doc.qt.io/qtforpython/PySide6/QtCore/QThread.html また、PySide で GUI を止めることなく重い処理をするサンプルとして、以下がありました。 - https://doc.qt.io/qtforpython/examples/example_corelib__threads.html 上記のサンプルでは `QThread` を継承し、`run` をオーバーライドしていますが、このやり方は良くないそうで、`moveToThread` を使って処理をスレッドに渡すやり方のほうが一般的なようです。 詳しくは以下で解説されています。 - https://qiita.com/hermit4/items/1606750332d1d2685bdb - https://www.fixes.pub/program/426883.html とはいえ、`moveToThread` を使った公式のサンプルが見当たらなかったので、知らない人は知らないんじゃないかなぁと思ったり・・。 というわけで、`QThread` と `moveToThread` 使ったスレッド化のサンプルです。 ## 環境 - Python 3.8.10 - PySide 6.2.2.1 ## サンプルコード 解説は後にして、まずはコード全体です。 ```py import sys, threading from PySide6.QtWidgets import QApplication, QWidget, QPushButton, QLabel, QVBoxLayout from PySide6.QtCore import Qt, QObject, QThread, Signal, Slot import shiboken6 import time class Worker(QObject): """バックグラウンドで処理を行うクラス """ countup = Signal(int) def __init__(self, parent=None): super().__init__(parent) self.__is_canceled = False def run(self): print(f'worker started, thread_id={threading.get_ident()}') count = 0 while not self.__is_canceled: count += 1 self.countup.emit(count) time.sleep(0.001) print(f'worker finished, thread_id={threading.get_ident()}') def stop(self): self.__is_canceled = True class MainWindow(QWidget): """メインウィンドウ """ def __init__(self, parent=None): super().__init__(parent) self.__thread = None layout = QVBoxLayout() button = QPushButton('Start') button.clicked.connect(self.__start) layout.addWidget(button) button = QPushButton('Stop') button.clicked.connect(self.__stop) layout.addWidget(button) self.__label = QLabel() layout.addWidget(self.__label) self.setLayout(layout) def __start(self): """開始 """ print(f'start, thread_id={threading.get_ident()}') self.__stop() self.__thread = QThread() self.__worker = Worker() self.__worker.moveToThread(self.__thread) # 別スレッドで処理を実行する # シグナルスロットの接続(self.__countup をスレッド側で実行させるために Qt.DirectConnection を指定) self.__worker.countup.connect(self.__countup, type=Qt.DirectConnection) # スレッドが開始されたら worker の処理を開始する self.__thread.started.connect(self.__worker.run) # ラムダ式を使う場合は Qt.DirectConnection を指定 #self.__thread.started.connect(lambda: self.__worker.run(), type=Qt.DirectConnection) # スレッドが終了したら破棄する self.__thread.finished.connect(self.__worker.deleteLater) self.__thread.finished.connect(self.__thread.deleteLater) # 処理開始 self.__thread.start() def __stop(self): """停止 """ print(f'stop, thread_id={threading.get_ident()}') if self.__thread and shiboken6.isValid(self.__thread): # スレッドが作成されていて、削除されていない if self.__thread.isRunning() or not self.__thread.isFinished(): print('thread is stopping') self.__worker.stop() self.__thread.quit() self.__thread.wait() print('thread is stopped') def __countup(self, count): """countup シグナルに対する処理 """ self.__label.setText(f'count={count}, thread_id={threading.get_ident()}') def closeEvent(self, event): """closeEvent のオーバーライド(ウィンドウを閉じたときにスレッドを終了させる) """ self.__stop() if __name__ == "__main__": app = QApplication(sys.argv) main_window = MainWindow() main_window.show() sys.exit(app.exec()) ``` カウントアップした変数の内容を表示するだけのシンプルなコードです。 スロットがどのスレッドで実行されているかわかるように、スレッドIDも表示しています。
## 簡単な解説 `Worker` が別スレッドで行いたい処理ですが、こいつ自身はスレッド云々を意識せず(どこで実行されるかを意識せず)、処理内容のみを責務としています。 どこで実行するかを意識するのは、処理を開始する `MainWindow` です。 ### シグナルスロット接続時の注意 以下の部分です。 ```py self.__worker.countup.connect(self.__countup, type=Qt.DirectConnection) ``` `self.__countup` は `MainWindow` 内の関数なので、普通に接続するとメインスレッド側で処理が行われてしまいます。 これをスレッド側で処理が行われるようにするには `Qt.DirectConnection` を指定する必要があります。 `countup` シグナルはスレッド側で動いている `Worker` で emit されるので、`Qt.DirectConnection` を指定することでシグナルを emit するスレッド側での実行を指定することになります。 ここらへん、以下で詳しく解説されています。 - https://qiita.com/hermit4/items/1606750332d1d2685bdb#connect%E3%81%AE%E7%A8%AE%E9%A1%9E - https://teratail.com/questions/168096 ちなみに、以下のスレッド開始時の処理ですが、 ```py self.__thread.started.connect(self.__worker.run) ``` `self.__worker` は `moveToThread` でスレッド側で処理を行うように指定しているので、 明示的に `Qt.DirectConnection` を指定しなくてもよいのですが、これを仮に以下のようにラムダ式などを使った場合は、上記と同じ理由で `Qt.DirectConnection` を指定する必要があります。 ```py self.__thread.started.connect(lambda: self.__worker.run(), type=Qt.DirectConnection) ``` 例えば、`self.__worker.run` に引数を与えて開始したい場合などはこういうケースもあるのではないかと思います。 でもその場合 `moveToThread` を使う意味がなくなりそうですが・・・。(実際、`moveToThread` を使わなくても上手く動きます。) ### 終了処理 スレッドが終了したら `deleteLater` でオブジェクトの破棄を行っています。 ```py self.__thread.finished.connect(self.__worker.deleteLater) self.__thread.finished.connect(self.__thread.deleteLater) ``` - https://tukunen13.hatenablog.jp/entry/2017/05/25/004652 また、スレッドを開始したままウィンドウを閉じたときのために、`closeEvent` をオーバーライドしてスレッドを停止するようにしています。 ### オブジェクトの判定 Start ボタンを押したときにスレッドを停止してから再度開始していますが、その際に既にスレッドが破棄されていないかどうか判定しています。 オブジェクトが破棄されているかどうかは `shiboken` を使います。 - https://stackoverflow.com/questions/11328219/how-to-know-if-object-gets-deleted-in-python ### スレッドの終了判定 `QThread` に `isRunning` と `isFinished` という関数がありますが、どっちを使えばいいのかいまいち分からなかったので両方見るようにしました。 ここらへん微妙です・・・。
## 参考 URL - https://stackoverflow.com/questions/1595649/threading-in-a-pyqt-application-use-qt-threads-or-python-threads - https://doc.qt.io/qtforpython/PySide6/QtCore/QThread.html - https://fereria.github.io/reincarnation_tech/11_PySide/02_Tips/06_qthread_01/ - https://srinikom.github.io/pyside-docs/PySide/QtCore/QThread.html - https://theolizer.com/cpp-school4/cpp-school4-8/ - https://code.tiblab.net/python/pyside/thread_ui_change - https://realpython.com/python-pyqt-qthread/ - https://tukunen13.hatenablog.jp/entry/2017/05/25/004652 - https://stackoverflow.com/questions/11328219/how-to-know-if-object-gets-deleted-in-python
tweet
facebook
Pocket
B!
はてブ
LINE
chevron_right
Translate
Popular Posts
ASP.NET Core で AdminLTE を使用する
TortoiseGit でコミットメッセージを変更する
TortoiseGit でリモートリポジトリのタグを削除する
image
NO IMAGE
C# と C/C++ のライブラリを混在させる場合の構成
image
NO IMAGE
TortoiseGit でブランチ間の差分を見る
image
NO IMAGE
外部 DLL を NuGet パッケージに含める方法
image
NO IMAGE
ASP.NET Core におけるフロントエンドのパッケージ管理
image
NO IMAGE
System.Net.Sockets.SocketException: 'アクセス許可で禁じられた方法でソケットにアクセスしようとしました。'
[Python] 書式指定で変数を使う
image
NO IMAGE
[C#] Dictionary の キーがタプルの場合に JSON へのシリアライズとデシリアライズを行う方法
Labels
.NET Core
31
.NET Framework
17
.NET Standard
2
AdminLTE
1
Apache
3
AppVeyor
2
AsciiDoc
3
ASP.NET Core
55
Atom
4
AWS
2
AWS Cloud9
4
blockdiag
1
Blogger
10
Bootstrap
3
C/C++
6
C#
106
CentOS
3
Chrome
1
Chronograf
3
Codecov
1
CSS
1
Docker
28
DokuWiki
4
Doxygen
1
draw.io
1
Electron.NET
2
Entity Framework Core
9
Excel
2
FFmpeg
2
Firefox
5
Git
12
GitBook
4
GitBucket
7
GitHub
7
GitLab
30
Go
1
Google
1
Google Cloud Platform
1
Grafana
5
HTML
5
IIS
8
InfluxDB
6
JavaScript
7
Jenkins
7
Linux
25
Log4View
1
MahApps.Metro
3
MaterialDesignInXamlToolkit
1
MVC
1
MVVM
6
NLog
3
Node.js
3
npm
1
OpenSSL
3
ownCloud
2
Pine Script
1
PlantUML
5
PowerShell
7
Prism
2
Python
11
Razor
3
Redmine
30
remark.js
2
rocketchat
4
Ruby
3
SignalR
1
Socket.IO
1
SonarQube
5
Sphinx
10
SQL Server
5
SQLite
1
t
1
TestLink
2
Tomcat
2
TortoiseGit
10
TortoiseSVN
2
Trading View
1
Travis CI
1
Ubuntu
13
Visual Studio
39
Visual Studio Code
9
Vue.js
8
Windows
56
Windows 10
4
Windows ADK
1
Windows API
2
Windows Embedded
4
wkhtmltopdf
2
Word
3
WPF
12
WSL
1
Xamarin
1
xUnit
5
アプリケーション
1
デザインパターン
1
テスト
3
バッチファイル
2
ぴよ
3
プログラミング
3
ライセンス
1
ラベル
3
ラベル1
2
英語
2
雑記
1
書籍
1
数学
1
正規表現
1
Blog Archive
▼
2022
(1)
▼
2月
(1)
例えばボタンをクリックしたときに重い処理を実行する場合など、そのまま処理すると GUI が固まって...
►
2021
(24)
►
5月
(7)
►
4月
(8)
►
3月
(2)
►
2月
(2)
►
1月
(5)
►
2020
(60)
►
12月
(1)
►
11月
(3)
►
10月
(3)
►
9月
(3)
►
8月
(3)
►
7月
(7)
►
6月
(7)
►
5月
(2)
►
4月
(6)
►
3月
(6)
►
2月
(7)
►
1月
(12)
►
2019
(92)
►
12月
(13)
►
11月
(9)
►
10月
(3)
►
9月
(2)
►
8月
(3)
►
7月
(5)
►
6月
(11)
►
5月
(6)
►
4月
(17)
►
3月
(9)
►
2月
(6)
►
1月
(8)
►
2018
(100)
►
12月
(1)
►
11月
(11)
►
10月
(8)
►
9月
(6)
►
8月
(10)
►
7月
(10)
►
6月
(8)
►
5月
(9)
►
4月
(8)
►
3月
(14)
►
2月
(4)
►
1月
(11)
►
2017
(117)
►
12月
(14)
►
11月
(20)
►
10月
(17)
►
9月
(19)
►
8月
(10)
►
7月
(8)
►
6月
(3)
►
5月
(6)
►
4月
(5)
►
3月
(2)
►
2月
(8)
►
1月
(5)
►
2016
(91)
►
12月
(5)
►
11月
(9)
►
10月
(11)
►
9月
(9)
►
8月
(6)
►
7月
(14)
►
6月
(14)
►
5月
(11)
►
4月
(10)
►
3月
(2)
►
2015
(23)
►
12月
(4)
►
11月
(2)
►
10月
(8)
►
9月
(8)
►
7月
(1)
►
2013
(3)
►
11月
(1)
►
9月
(1)
►
7月
(1)
►
2012
(2)
►
7月
(1)
►
6月
(1)
►
2011
(1)
►
9月
(1)
►
2009
(1)
►
7月
(1)
►
2008
(2)
►
11月
(1)
►
7月
(1)
►
2007
(3)
►
10月
(3)