GTK +はすべての人に適していますが、マルチスレッドアプリケーションで作業する場合には大きな問題があります。 GTK自体はスレッドセーフですが、ユーザーによる強制ブロックが必要です。 2番目の問題は、ロックがミューテックスを介して実装され、ロックを厳密に1回呼び出す必要があることです。そうしないと、Linuxでコードがハングし、Windowsで正常に動作します。
アクセス方法「スレッドはGUIにアクセスしますが、主なことはロックを引き起こすことです」が失敗であることが判明しました。
UP :コードは
Githubにアップロードされ、「メイン」プロジェクトのコードの最新バージョンでわずかに更新されます。 外観の変更(秒ではなく期間、および問題のある領域を検索するための誤ったストリームの使用のログが追加されました。
これに関連して、私の小さなプロジェクトで、ストリームを使用して最も問題のない作業を編成する次の方法に取り組みました。
- GTKを呼び出すと、スレッドは1つだけ生成されます。 GUIに対するすべての変更は、専用の状態更新機能によって行われます。
- すべてのロックは、共通のリエントラントブロッカーを通じて行われます。
- ブロッカーは、「間違った」ストリームからのGUIへのアクセスも監視し、ログを記録します。
GUIパーツの一般的な構造
主な部分はgui.pyモジュールで構成され、次の「良い」ものをエクスポートします。
GtkLocker = CGtkLocker()
アプリケーションの動作は次のようになります。
import gui, gtk def InitApp(): """ """ with GtkLocker: window = gtk.Window(gtk.WINDOW_TOPLEVEL) window.set_title(u" ") window.connect("destroy", gui.GUIstop) window.realize() vbox = gtk.VBox() window.add(vbox) label = gtk.Label("Text label") vbox.add(label) window.show_all() def __main__(): gui.GuiCall( InitApp ) gui.GUI() if __name__ == '__main__': __main__() sys.exit(0)
このアプリケーションは、「テキストラベル」というテキストを含むウィンドウを表示するだけです。 人間でやってみましょう。すべてのアプリケーションコードをクラスに入れて、ボタンを追加します。
class CMyApp(object): def __init__(self): self.label = None self.times = 0 def CreateGui(self): with gui.GtkLocker: window = gtk.Window(gtk.WINDOW_TOPLEVEL) window.set_title(u" ") window.connect("destroy", gui.GUIstop) window.realize() vbox = gtk.VBox() window.add(vbox) label = gtk.Label("Welcome to our coool app!") vbox.pack_start(label) label = gtk.Label("Here will be counter") self.label = label vbox.pack_start(label) button = gtk.Button("Press me!") button.connect("clicked", self.Click) vbox.pack_start(button) window.show_all() @gui.GtkLocked def Click(self, widget): self.times += 1 self.label.set_text("You pressed button %d times" % self.times) MyApp = CMyApp() def InitApp(): """ """ MyApp.CreateGui()
それで私たちは何をしましたか オブジェクトを作成しました。このメソッドの
__init__
メソッドは、メソッドの将来のフィールドを単純に準備し、実際の作成はすべて、一般的なイベント処理ループから既に呼び出される
CreateGui
関数で実行されます。
Click
メソッドの対象となる魔法について、注意を払って
gui.GtkLocked
。ラッパー
gui.GtkLocked
マークされています。 これは、このメソッドがGUIイベント処理メソッドであり、connectを介して厳密に呼び出されることを意味します。これは、メソッドが呼び出された時点で既にGTKロックを持っていることを意味します。 このラッパーは、「ロック済み」ブロッカーGUIの状態を実装するため、関数内でロックを使用しても問題は発生しません。
海を便利にする2番目のボタンを追加します。
class CMyApp(object): def CreateGui(self): with gui.GtkLocker: .... button = gtk.Button("Or me!") button.connect("clicked", gui.GtkLocked(self.Count)) vbox.pack_start(button) .... @gui.GtkLocked def Click(self, widget): self.Count() gui.GuiSecondCall( self.Count ) def Count(self, *args, **kwargs): with gui.GtkLocker: self.times += 1 self.label.set_text("You pressed button %d times" % self.times)
そのため、前のボタンの
Click
メソッドを一般的な
Count
メソッドの呼び出しに変更し、最大1秒の遅延を伴う遅延呼び出しに変更しました。これにより、火星の天気に関係なくコードをカウントおよび更新し、2番目のボタンに直接
Count
を掛けます。
Count
メソッドは
connect
だけでなく呼び出しも含む
connect
、
@gui.GtkLocked
をハングさせることはできません-メソッドはブロックされていないコンテキスト(たとえば、アイドルイベントで呼び出される)から呼び出すことができるので、
gui.GtkLocked
を
connect
瞬間。 その結果、
Count
メソッドはブロックされていないコンテキストから呼び出すことができ、ロック自体をキャプチャしますが、同時にイベントにバインドされ、別のイベントハンドラーによって呼び出されます。
GtkLocker
と
GtkLocked
魔法により
GtkLocked
デッドロックは発生せず、すべてが機能します。
それでは、プログレスバーの殿下と、そのプロセスでコンテンツを更新する複雑なバックグラウンドプロセスを追加しましょう。
class CMyApp(object): def CreateGui(self): with gui.GtkLocker: .... progress = gtk.ProgressBar() self.progress = progress vbox.pack_start(progress) T = threading.Thread(name="Background work", target=self.Generate) T.setDaemon(1) T.start() @gui.GtkLocked def UpdateProgress(self): self.progress.pulse() def Generate(self): while(True): time.sleep(0.3) gui.GuiIdleCall( self.UpdateProgress )
したがって、ここでは、
Generate
メソッドはバックグラウンドに対して機能し、0.3秒ごとに進行状況を更新したいため、
UpdateProgress
を実行
UpdateProgress
入れ
UpdateProgress
。
UpdateProgress
はGUIスレッドのコンテキストで実行されるため、すべてが機能します。 しかし、完了するのに必要な時間がわからない場合はどうなりますか? くしゃみをすべて更新しますか? 0.3を0.001に置き換えて、結果を賞賛します。 いいえ、これはオプションではありません。 時間測定を追加し、人為的に更新を遅くしますか? 通常はオプションではありません。 たぶん
GuiIdleCall
代わりに
GuiIdleCall
を
GuiSecondCall
ますか? 試してみましょう...うーん。 毎秒、この1秒間に完了したすべてのイベントの急激な更新があります。 ホラー
別のバックグラウンドプロセスを追加し、それに「スマート」更新メソッドを追加しましょう。
class CMyApp(object): def CreateGui(self): with gui.GtkLocker: .... fastprogress = gtk.ProgressBar() self.fastprogress = fastprogress vbox.pack_start(fastprogress) T = threading.Thread(name="Heavy background work", target=self.GenerateFast) T.setDaemon(1) T.start() @gui.SecondUpdater def SingleUpdateProgress(self): self.fastprogress.pulse() def GenerateFast(self): while(True): time.sleep(0.001) self.SingleUpdateProgress()
魔法、大喜び! 状態更新関数を宣言し、必要なラッパー
@gui.SecondUpdater
または
@gui.IdleUpdater
をハングアップする
@gui.IdleUpdater
で、メソッドはGUIスレッドのコンテキストで1秒あたり1回または空き時間に自動的に呼び出されます。 ラッパーが原因で、行のメソッドの二重開始は除外され、呼び出されたかどうかにかかわらず不必要なアカウンティングコードを必要とせず、実行キューに入れることを考慮する必要はありません。
ボンネットの下
それでは、gui.pyの中に何が蓄積されているかを詳しく見ていきましょう。
一般的なコードは複雑ではなく、初期化だけです:
from __future__ import with_statement import logging, traceback logging.basicConfig(level=logging.DEBUG, filename='debug.log', filemode='a', format='%(asctime)s %(levelname)-8s %(module)s %(funcName)s %(lineno)d %(threadName)s %(message)s') import pygtk pygtk.require('2.0') import gtk gtk.gdk.threads_init() import gobject, gtk.glade, Queue, sys, configobj, threading, thread from functools import wraps import time, os.path gtk.gdk.threads_enter() IGuiCaller = Queue.Queue() IGuiIdleCaller = Queue.Queue() IGuiSecondsCaller = Queue.Queue() IdleCaller = [ None ] IdleCallerLock = threading.Lock() gtk.gdk.threads_leave() class CGtkLocker: ....
次に、最も興味深いラッパーを見てみましょう。 そのため、最も重要なのはリエントラントロックの実装です。
class CGtkLocker: def __init__(self):
「
with GtkLocker
」では、GUIで動作する部分
with GtkLocker
必要があることを理解する必要があります。
そしてほとんどの場合、すべての呼び出しが異なるスレッドから行われたとしても、これは機能します。したがって、
threads_enter
/
threads_leave
作成されます。 それは時々、すべてが当面動作するだけで、突然GTKの深部のどこかで地殻に落ちます。
既にロック内にあるGTKカーネルによって呼び出されるイベントメソッドをマークできるようにする
GtkLocker
と
GtkLocker
ラッパー。 レベル0で呼び出されると、ロックレベルのレベルが上がり、それによって自分たちが
threads_enter
/
threads_leave
呼び出さないようにします。
def GtkLocked(f): @wraps(f) def wrapper(*args, **kwds): with GtkLocker.lock: if GtkLocker.thread == None or GtkLocker.thread==thread.get_ident(): GtkLocker.thread = thread.get_ident() GtkLocker.locked += 1 WeHold = True else: print "!ERROR! GtkLocked for non-owned thread!" logging.error("GtkLocked for non-owned thread!") WeHold = False ret = None try: return f(*args, **kwds) finally: if WeHold: with GtkLocker.lock: GtkLocker.locked -= 1 if GtkLocker.locked == 0: GtkLocker.thread = None return wrapper
さて、最後の魔法のパス:
IdleUpdater
/
SecondUpdater
:
def IdleUpdater(f): @wraps(f) def wrapper(*args, **kwds): self = len(args)>0 and isinstance(args[0], object) and args[0] or f if '_idle_wrapper' not in self.__dict__: self._idle_wrapper = {} def runner(): if self._idle_wrapper[f]: try: return f(*args, **kwds) finally: self._idle_wrapper[f] = False return None if f not in self._idle_wrapper or not self._idle_wrapper[f]: self._idle_wrapper[f] = True
呼び出しているメソッドのオブジェクトでは、
_idle_wrapper
ディクショナリが
_idle_wrapper
れます。このディクショナリでは、指定されたメソッドが既に実行キューに置かれているかどうかを監視します。そうでない場合は、このメソッドを実行し、記憶されている実行のフラグをクリアするスタートアップラッパーを設定して追加することを覚えています。 その結果、メソッドの最初の呼び出しは、その起動を
GuiIdleCall
(または
*Seconds*
)キューに追加し、実行されるまで繰り返される呼び出しは単に無視されます。
コードと有用な情報をダウンロードする
この例のすべてのソースが利用可能です:
pygtk-demo.tar.bz2PyGTKの既知の問題に関する有用な情報については、公式
FAQをご覧
ください 。
PyGTKでの記述に関する質問については、ほとんどの場合、
チュートリアルと
マニュアルを参照します 。