7

Python (pyGTK を使用) での GUI 開発の初心者として、私はスレッド化について学び始めたばかりです。自分のスキルをテストするために、スタート/ストップ ボタンを備えたシンプルで小さな GTK インターフェイスを作成しました。目標は、クリックするとスレッドが開始され、GUI の応答性を維持しながら、テキスト ボックス内の数値をすばやくインクリメントすることです。

GUI は正常に動作していますが、スレッドに問題があります。単純な問題かもしれませんが、私の心はその日の揚げ物のことです。以下に、最初に Python インタープリターからのトラックバックを貼り付け、次にコードを貼り付けました。http://drop.io/pxgr5idにアクセスしてダウンロードできます。リビジョン管理に bzr を使用しているので、変更して再ドロップしたい場合は、変更をコミットしてください。また、コードをhttp://dpaste.com/113388/に貼り付けています。これは、行番号が含まれている可能性があるためです。このマークダウンは頭痛の種です。

更新 1 月 27 日 15:52 EST: 少し更新されたコードは次の場所にあります: http://drop.io/threagui/asset/thread-gui-rev3-tar-gz

トレースバック

crashsystems@crashsystems-laptop:~/Desktop/thread-gui$ python threadgui.pybtnStartStop clicked
Traceback (most recent call last):
  File "threadgui.py", line 39, in on_btnStartStop_clicked
    self.thread.stop()
  File "threadgui.py", line 20, in stop
    self.join()
  File "/usr/lib/python2.5/threading.py", line 583, in join
    raise RuntimeError("cannot join thread before it is started")
RuntimeError: cannot join thread before it is started
btnStartStop clicked
threadStop = 1
btnStartStop clicked
threadStop = 0
btnStartStop clicked
Traceback (most recent call last):
  File "threadgui.py", line 36, in on_btnStartStop_clicked
    self.thread.start()
  File "/usr/lib/python2.5/threading.py", line 434, in start
    raise RuntimeError("thread already started")
RuntimeError: thread already started
btnExit clicked
exit() called

コード

#!/usr/bin/bash
import gtk, threading

class ThreadLooper (threading.Thread):
    def __init__ (self, sleep_interval, function, args=[], kwargs={}):
        threading.Thread.__init__(self)
        self.sleep_interval = sleep_interval
        self.function = function
        self.args = args
        self.kwargs = kwargs
        self.finished = threading.Event()

    def stop (self):
        self.finished.set()
        self.join()

    def run (self):
        while not self.finished.isSet():
            self.finished.wait(self.sleep_interval)
            self.function(*self.args, **self.kwargs)

class ThreadGUI:
    # Define signals
    def on_btnStartStop_clicked(self, widget, data=None):
        print "btnStartStop clicked"
        if(self.threadStop == 0):
            self.threadStop = 1
            self.thread.start()
        else:
            self.threadStop = 0
            self.thread.stop()
        print "threadStop = " + str(self.threadStop)

    def on_btnMessageBox_clicked(self, widget, data=None):
        print "btnMessageBox clicked"
        self.lblMessage.set_text("This is a message!")
        self.msgBox.show()

    def on_btnExit_clicked(self, widget, data=None):
        print "btnExit clicked"
        self.exit()

    def on_btnOk_clicked(self, widget, data=None):
        print "btnOk clicked"
        self.msgBox.hide()

    def on_mainWindow_destroy(self, widget, data=None):
        print "mainWindow destroyed!"
        self.exit()

    def exit(self):
        print "exit() called"
        self.threadStop = 1
        gtk.main_quit()

    def threadLoop(self):
        # This will run in a thread
        self.txtThreadView.set_text(str(self.threadCount))
        print "hello world"
        self.threadCount += 1

    def __init__(self):
        # Connect to the xml GUI file
        builder = gtk.Builder()
        builder.add_from_file("threadgui.xml")

        # Connect to GUI widgets
        self.mainWindow = builder.get_object("mainWindow")

        self.txtThreadView = builder.get_object("txtThreadView")
        self.btnStartStop = builder.get_object("btnStartStop")
        self.msgBox = builder.get_object("msgBox")
        self.btnMessageBox = builder.get_object("btnMessageBox")
        self.btnExit = builder.get_object("btnExit")
        self.lblMessage  = builder.get_object("lblMessage")
        self.btnOk = builder.get_object("btnOk")

        # Connect the signals
        builder.connect_signals(self)

        # This global will be used for signaling the thread to stop.
        self.threadStop = 1

        # The thread
        self.thread = ThreadLooper(0.1, self.threadLoop, (1,0,-1))
        self.threadCounter = 0

if __name__ == "__main__":
    # Start GUI instance
    GUI = ThreadGUI()
    GUI.mainWindow.show()
    gtk.main()
4

5 に答える 5

9

PyGTK を使用したスレッド化は、正しく実行したい場合には少し注意が必要です。基本的に、メイン スレッド以外のスレッド内から GUI を更新しないでください (GUI ライブラリの一般的な制限)。通常、これはタイムアウト機能を使用して定期的に読み取られる (ワーカーと GUI 間の通信用の) キューに入れられたメッセージのメカニズムを使用して PyGTK で行われます。このトピックについてローカル LUG でプレゼンテーションを行ったら、Google Code リポジトリからこのプレゼンテーションのサンプル コードを取得できます。MainWindowのクラスforms/frmmain.py、特にメソッド_pulse()と で行われることを見てくださいon_entry_activate()(そこでスレッドが開始され、アイドル タイマーが作成されます)。

def on_entry_activate(self, entry):
    text = entry.get_text().strip()
    if text:
        store = entry.get_completion().get_model()
        if text not in [row[0] for row in store]:
            store.append((text, ))
        thread = threads.RecommendationsFetcher(text, self.queue)# <- 1
        self.idle_timer = gobject.idle_add(self._pulse)# <- 2
        tv_results = self.widgets.get_widget('tv_results')
        model = tv_results.get_model()
        model.clear()
        thread.setDaemon(True)# <- 3
        progress_update = self.widgets.get_widget('progress_update')
        progress_update.show()
        thread.start()# <- 4

このようにして、アプリケーションは (GTK による) "アイドル" 時に GUI を更新し、フリーズすることはありません。

  • 1: スレッドを作成する
  • 2: アイドル タイマーを作成する
  • 3: スレッドをデーモン化して、スレッドの完了を待たずにアプリを閉じることができるようにします
  • 4: スレッドの開始
于 2009-01-28T10:13:37.540 に答える
3

一般的に、可能な場合はスレッドを避けることをお勧めします。スレッド化されたアプリケーションを正しく作成することは非常に困難であり、正しく作成されたことを知ることはさらに困難です。GUIアプリケーションを作成しているので、非同期フレームワーク内でアプリケーションを作成する必要があるため、その方法を視覚化する方が簡単です。

理解しておくべき重要なことは、GUIアプリケーションはほとんど何もしていないということです。ほとんどの時間、OSが何かが起こったことを通知するのを待ちます。長時間実行されるコードを記述してブロックされないようにする方法を知っている限り、このアイドル時間に多くのことを実行できます。

タイムアウトを使用すると、元の問題を解決できます。遅延後に関数をコールバックするようにGUIフレームワークに指示し、その遅延をリセットするか、別の遅延呼び出しを開始します。

もう1つのよくある質問は、GUIアプリケーションでネットワークを介して通信する方法です。ネットワークアプリは、多くの待機を行うという点でGUIアプリに似ています。ネットワークIOフレームワーク(Twistedなど)を使用すると、アプリケーションの両方の部分を競合的にではなく協調的に待機させることが容易になり、追加のスレッドの必要性が軽減されます。

長時間実行される計算は、同期ではなく反復的に記述でき、GUIがアイドル状態のときに処理を実行できます。ジェネレーターを使用して、Pythonでこれを非常に簡単に行うことができます。

def long_calculation(param, callback):
    result = None
    while True:
        result = calculate_next_part(param, result)
        if calculation_is_done(result):
            break
        else:
            yield
    callback(result)

を呼び出すlong_calculationとジェネレータオブジェクトが提供され、ジェネレータオブジェクトを呼び出すと、またはのいずれか.next()に到達するまでジェネレータが実行されます。時間があるときにGUIフレームワークに呼び出すように指示するだけで、最終的には結果とともにコールバックが呼び出されます。yieldreturnlong_calculation(some_param, some_callback).next

GTKについてはよくわからないので、どのgobject関数を呼び出すべきかわかりません。ただし、この説明を使用すると、ドキュメントで必要な機能を見つけることができるはずです。最悪の場合、関連するIRCチャネルで質問することもできます。

残念ながら、一般的な場合の適切な答えはありません。何をしようとしているのかを正確に明確にすると、その状況でスレッドが必要ない理由を説明しやすくなります。

于 2009-01-27T05:10:24.907 に答える
1

停止したスレッド オブジェクトを再開することはできません。しようとしないでください。代わりに、オブジェクトを完全に停止して結合した後に再起動する場合は、オブジェクトの新しいインスタンスを作成します。

于 2009-01-27T06:24:06.617 に答える
0

スレッドやアイドル処理などで作業をクリーンアップするのに役立つさまざまなツールを試しました。

make_idle は、バックグラウンドでタスクを協調的に実行できる関数デコレーターです。これは、UI スレッドで 1 回実行するのに十分短く、アプリの応答性に影響を与えないものと、特殊な同期で完全なスレッドを実行するものとの間の適切な中間点です。装飾された関数内では、「yield」を使用して処理を GUI に戻します。これにより、応答性を維持し、次に UI がアイドル状態になったときに中断したところから関数を再開できます。したがって、これを開始するには、装飾された関数に idle_add を呼び出すだけです。

def make_idler(func):
    """
    Decorator that makes a generator-function into a function that will
continue execution on next call
    """
    a = []

    @functools.wraps(func)
    def decorated_func(*args, **kwds):
        if not a:
            a.append(func(*args, **kwds))
        try:
            a[0].next()
            return True
        except StopIteration:
            del a[:]
            return False

    return decorated_func

もう少し処理を行う必要がある場合は、必要に応じてコンテキスト マネージャーを使用して UI スレッドをロックし、コードを少し安全にすることができます。

@contextlib.contextmanager
def gtk_critical_section():
    gtk.gdk.threads_enter()
    try:
        yield
    finally:
        gtk.gdk.threads_leave()

それであなたはただすることができます

with gtk_critical_section():
    ... processing ...

私はまだそれを終えていませんが、純粋にアイドル状態で純粋にスレッドで行うことを組み合わせることで、yield の後の次のセクションが実行されるかどうかを伝えることができるデコレータ (まだテストされていないため、投稿されていません) があります。 UI のアイドル時間またはスレッドで。これにより、UI スレッドで何らかのセットアップを行い、バックグラウンド処理を行うために新しいスレッドに切り替えてから、UI のアイドル時間に切り替えてクリーンアップを行うことができ、ロックの必要性を最小限に抑えることができます。

于 2009-01-29T14:28:03.107 に答える
0

私はあなたのコードを詳しく見ていません。しかし、あなたの問題には2つの解決策があります:

スレッドは一切使用しないでください。代わりに、次のようにタイムアウトを使用します。

import gobject

i = 0
def do_print():
    global i
    print i
    i += 1
    if i == 10:
        main_loop.quit()
        return False
    return True

main_loop = gobject.MainLoop()
gobject.timeout_add(250, do_print)
main_loop.run()

スレッドを使用する場合、次のようにガードして、GUI コードが同時に 1 つのスレッドからのみ呼び出されるようにする必要があります。

import threading
import time

import gobject
import gtk

gtk.gdk.threads_init()

def run_thread():
    for i in xrange(10):
        time.sleep(0.25)
        gtk.gdk.threads_enter()
        # update the view here
        gtk.gdk.threads_leave()
    gtk.gdk.threads_enter()
    main_loop.quit()
    gtk.gdk.threads_leave()

t = threading.Thread(target=run_thread)
t.start()
main_loop = gobject.MainLoop()
main_loop.run()
于 2009-02-02T17:18:43.573 に答える