PyGTK:ストリームとラッパーマジック

GTK +はすべての人に適していますが、マルチスレッドアプリケーションで作業する場合には大きな問題があります。 GTK自体はスレッドセーフですが、ユーザーによる強制ブロックが必要です。 2番目の問題は、ロックがミューテックスを介して実装され、ロックを厳密に1回呼び出す必要があることです。そうしないと、Linuxでコードがハングし、Windowsで正常に動作します。



アクセス方法「スレッドはGUIにアクセスしますが、主なことはロックを引き起こすことです」が失敗であることが判明しました。



UP :コードはGithubにアップロードされ、「メイン」プロジェクトのコードの最新バージョンでわずかに更新されます。 外観の変更(秒ではなく期間、および問題のある領域を検索するための誤ったストリームの使用のログが追加されました。



これに関連して、私の小さなプロジェクトで、ストリームを使用して最も問題のない作業を編成する次の方法に取り組みました。

  1. GTKを呼び出すと、スレッドは1つだけ生成されます。 GUIに対するすべての変更は、専用の状態更新機能によって行われます。
  2. すべてのロックは、共通のリエントラントブロッカーを通じて行われます。
  3. ブロッカーは、「間違った」ストリームからのGUIへのアクセスも監視し、ログを記録します。


GUIパーツの一般的な構造



主な部分はgui.pyモジュールで構成され、次の「良い」ものをエクスポートします。

GtkLocker = CGtkLocker() #  def GtkLocked(f): #    gui,        def IdleUpdater(f): #    ,      gui def SecondUpdater(f): #    ,       def GUIstop(): #       GUI def GuiCall(Func): #    GUI ""  def GuiIdleCall(Func): #    GUI       def GuiSecondCall(Func): #       def GUI(): #    
      
      







アプリケーションの動作は次のようになります。

 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: .... #    GtkLocker = CGtkLocker() #   GUI  --   main_quit,   GUI  @IdleUpdater def GUIstop(*args, **kwargs): gtk.main_quit() # ,     . #    (gobject)  , #  threading.Queue,     . def GuiCall(Func): IGuiCaller.put(Func) with IdleCallerLock: if IdleCaller[0] == False: gobject.idle_add(GUIrun) IdleCaller[0] = True def GuiIdleCall(Func): IGuiIdleCaller.put(Func) with IdleCallerLock: if IdleCaller[0] == False: gobject.idle_add(GUIrun) IdleCaller[0] = True def GuiSecondCall(Func): IGuiSecondsCaller.put(Func) #    GUI  def GUIrun(): #     GuiCaller try: Run = IGuiCaller.get(0) #   idle ,  GTK   # ,   ,    #      "with GtkLocker:" ,  @GtkLocked with GtkLocker: Run() except Queue.Empty: #    --  GuiIdleCaller try: Run = IGuiIdleCaller.get(0) with GtkLocker: Run() except Queue.Empty: #     -- ,   with GtkLocker: IdleCaller[0] = False return False return True #   :     #     def GUIrunSeconds(): try: with GtkLocker: while (True): Run = IGuiSecondsCaller.get(0) Run() except Queue.Empty: pass return True #    def GUI(): #   gobject.idle_add(GUIrun) IdleCaller[0] = True gobject.timeout_add(1000, GUIrunSeconds) #     gtk.main gtk.gdk.threads_enter() #    GtkLocker, #   gtk.main    GtkLocker.FREE() #     GUI  gtk.main() #   main_quit,   main    gtk.gdk.threads_leave()
      
      







次に、最も興味深いラッパーを見てみましょう。 そのため、最も重要なのはリエントラントロックの実装です。

 class CGtkLocker: def __init__(self): #       self.lock = threading.Lock() self.locked = 1 # ,      self.thread = thread.get_ident() #   ,     GUI    self.mainthread = self.thread self.warn = True #   ,   N  # ,     with  def __enter__(self): #       with self.lock: DoLock = (thread.get_ident()!=self.thread) #   ,   -       if self.warn and self.mainthread != thread.get_ident(): logging.error("GUI accessed from wrong thread! Traceback: "+"".join(traceback.format_stack())) #        with  ,      if DoLock: #        gtk.gdk.threads_enter() #             with self.lock: self.thread = thread.get_ident() #  __enter__    self.locked += 1 return None #     with  (  -- ,   ..) def __exit__(self, exc_type, exc_value, traceback): #      ,       with self.lock: self.locked -= 1 if self.thread!=thread.get_ident(): print "!ERROR! Thread free not locked lock!" logging.error("Thread free not locked lock!") sys.exit(0) else: if self.locked == 0: self.thread = None gtk.gdk.threads_leave() return None #    :      . def FREE(self): self.locked -= 1 self.thread = None if self.locked != 0: print "!ERROR! Main free not before MAIN!" logging.error("Main free not before MAIN!") sys.exit(0) GtkLocker = CGtkLocker()
      
      







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 #  SecondUpdater   GuiSecondCall GuiIdleCall( runner ) return wrapper
      
      







呼び出しているメソッドのオブジェクトでは、 _idle_wrapper



ディクショナリが_idle_wrapper



れます。このディクショナリでは、指定されたメソッドが既に実行キューに置かれているかどうかを監視します。そうでない場合は、このメソッドを実行し、記憶されている実行のフラグをクリアするスタートアップラッパーを設定して追加することを覚えています。 その結果、メソッドの最初の呼び出しは、その起動をGuiIdleCall



(または*Seconds*



)キューに追加し、実行されるまで繰り返される呼び出しは単に無視されます。

コードと有用な情報をダウンロードする



この例のすべてのソースが利用可能です: pygtk-demo.tar.bz2

PyGTKの既知の問題に関する有用な情報については、公式FAQをご覧ください

PyGTKでの記述に関する質問については、ほとんどの場合、 チュートリアルマニュアルを参照します



All Articles