PySideでIDLEイベントを捕まえるには??

 そもそもの話 PySideにはIDLEイベントがなくて、どうしよう?ってなったんですが、ググったら頭いい人がいました。

 emit SIGNAL when GUI Thread is idle in Qt?

IDLEイベントというのは、Windowプログラムが「現在お手隙な状態です」っていうタイミングを示すイベントって意味です。
要するに、GUIがなにもしてないので、なんか仕事ありませんか?って言ってくれる感じです。

 
 元はC++のコードなんですが、ざっくりPySide向けに書き直してみたんですが、これでいいのか良くわかんないんです…
 なんせイベントを上げたら次のaboutToBlockが発生するので、この実装を使うと実際には1コア占有しちゃう。
 IDLEで発行したイベントはカウントしない様にして欲しかったりするんですけどね…

python3x: parameterとして出てくる*argsと**kwargsって何を意味するの?

これも掲題の記事を見つけたメモ。

python3x: parameterとして出てくる*argsと**kwargsって何を意味するの?

 タプルの場合は引数の頭に*(アスタリスク1個)、辞書の場合は引数の頭に**(アスタリスク2個)付けていると、自動的に引数を展開して扱ってくれるんだけど、その辺の話。
 意外と必要になった時に、どうすんだっけ?って暫し考えることがあるので(特に辞書の時に…)メモっておく。。

Qtでスレッドを使う前に知っておこう

スレッド関連を調べていて、掲題の記事を見つけたのでメモ。

Qtでスレッドを使う前に知っておこう

内容としては、How To Really, Truly Use QThreads; The Full Explanationというブログ記事で語られる方法が最近のスレッド利用時の基本的なお作法になってますよ。的な話。

C++記事なんで、Python(PySide)で書き下してみたのが、こちら。
※全部のシグナルがこれで正しい設定なのか若干わかってないので、参考程度に。

いっぱいスレッド回そうと思ったら、こんな感じ?
※同時に表示されるタイミングがあるので、セマフォを使って順に処理するようにした。あと、処理終了時にウィンドウを閉じる設定も追加。

裏でスレッドを回しつつPySideのGUIの動作を滑らかにしたい

冒頭が一番最後、結論的な話で始めるけれど…

 実際に実装を書いて実行してみると、やっぱり場合により動作がおかしくて、「アプリケーションが停止しました」ってアプリが落ちたりする。
 ログを取ってみると、どうやら裏のプロセス実行が早すぎて表のUI処理が終わらないうちに次々と裏から要求が来てしまう。
 その為に表示処理が多重に呼び出されて、呼び出された関数が多すぎてスタックを食いつぶしているっぽい…という結論になった。

 ここにはまだ書いてないけど、裏から送られる処理を1つずつ順に片付けて、表の情報反映に必要な関数実行が多重化しないように制限を加えたら、安定して動くようになった。

 プロセス処理は、いろいろPythonを使ってると意識しなくてよかったことを意識させられてしまう。
 Pythonではなかなかしんどい部類の処理かもしれない。

うまく行かず、考え直してみた

 いろいろやってみて(後述)、PC替えたら正常に動作しないことが判明。
 調べてみると、PySide(というかPyQt?)は別スレッドから動かすことを許していない模様。
 自分のPCは4スレッドしか回らないのだけど、PCのスペックが高くて10~20スレッドが平気で動くハイスペックなPCだと、「Warning: QPixmapがどうたら」のようなメッセージがstdoutかstderrに吐かれてしまった…無念。

 
 どうにも、他の方法を検討する必要がある。
 とりあえず、ディレクトリ取る程度だとさほど処理を食わないのでスレッドでも平気だが、その後にファイルを読み込んだり、更に内容を読み取るような場合は別プロセス起動を考えないと、処理スレッドに移る度に数秒停滞という事が起こってしまう。

 感覚的には、処理毎に10ms程度のオーダーで処理を完了するような形にまとめないと、GUIが思うように動かなくなると思う。

 別スレッドで処理するサンプルをメモっておく。

いろいろやった事柄(時系列的には、ここがはじまり。)

参考ページ1
参考ページ2

 最初は参考ページ1で頑張っていたけど、どうにもならなくて…
 いろいろ試した上に、結局Pythonの並列処理モジュールを使わないとダメだってこと(※)が参考ページ2をベースにいろいろ試してみておおまかに理解できたと思う。
 ※掲題のGUIをスムーズに動かすって事が、厳しいってこと。

なにが引っ掛かるのか

 端的に言えば、PythonのGILの仕組みのせいで(いくらスレッドを複数回したところで)実際にはそれは並列処理ではないので、バトンリレーの要領でタスク切替が行われなければ無駄にスレッドが空回りしてしまう。

 処理A –> 処理B –> (処理A) .. みたいな流れをしたいのに、
 実際には、処理A ——-(不要な間)————> 処理B —(不要な間)–> (処理A) —(不要な間)–> … のようなことが起こる。

 不要な間の時には、処理が無駄な空回りをしているか、やって欲しくない処理を延々続けていたりする。
 無駄な空回りをしていると当然その間ほかのスレッドの処理は停滞してしまうので、GUIに至っては応答なし状態が続き、最悪アプリケーションエラーで不意に落ちることも。。

Pythonの並列処理モジュール

 この辺のモジュールのお世話になった。
 threading — スレッドベースの並列処理(@Python Doc)
 queue — 同期キュークラス

 簡単に言うと、並列処理モジュールを仲介させることでスムーズにスレッド間の処理が切り替わってくれるようになるので、無駄にスレッドが空回りしてしまう状況を回避できる。(ノンプリエンティブ・マルチタスクのイメージ。なるべく短い時間で処理を切り上げ、ほかに処理を渡す…という書き方。高速に切り替わるほどに、滑らかに処理されるようになる。)

 処理A –> 処理B –> (処理A) .. みたいな流れを作り出せる。

 今回は、「処理A -(queue)-> 処理B -(queue)-> (処理A)」 .. みたいに、Queueでバトンを渡す感じに作った。
 でもlistWidgetの都合…なのか、登録中にやたらpaint処理が走るせいで、ウィンドウが白くなっていることが多くてあまりよろしくない感じ。
 描画タイミングとかを考えると、登録方法にも手を入れないとダメっぽい…

その後

 いろいろゴチャゴチャ書いてしまったけど、結局のところ今回の件の場合はセマフォで解決(※)という話だった。

 ※実際は解決どころか、ここからがドツボ本番で…PCを変えたらWarning連発で、記事冒頭の考え直しの項に進みます。
  ただ処理的にダメって話ではなくて、それじゃまだ対策が足りない…という話だったんですが。

 参考ページ3