문제

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()
도움이 되었습니까?

해결책

Pygtk를 사용한 스레딩은 제대로하고 싶다면 약간 까다 롭습니다. 기본적으로 메인 스레드 (GUI LIBS의 공통 제한) 이외의 다른 스레드 내에서 GUI를 업데이트해서는 안됩니다. 일반적으로 이것은 시간 초과 기능을 사용하여 주기적으로 읽는 대기 메시지 (작업자와 GUI 간의 통신)의 메커니즘을 사용하여 Pygtk에서 수행됩니다. 이 주제에 대한 내 지역 러그에 대한 프레젠테이션이 있으면이 프레젠테이션에 대한 예제 코드를 가져올 수 있습니다. Google 코드 저장소. 살펴보십시오 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 : 스레드를 시작하십시오

다른 팁

일반적으로 가능한 경우 실을 피하는 것이 좋습니다. 스레드 응용 프로그램을 올바르게 작성하는 것은 매우 어렵고, 당신이 그것을 제대로 얻었음을 알기가 더 어렵습니다. GUI 애플리케이션을 작성하기 때문에 이미 비동기 프레임 워크 내에 응용 프로그램을 작성해야하므로 그렇게하는 방법을 시각화하는 것이 더 쉽습니다.

알아야 할 중요한 것은 GUI 애플리케이션이 아무것도하지 않는다는 것입니다. OS가 무슨 일이 일어 났다고 말하기를 기다리는 대부분의 시간을 보냅니다. 장기 실행 코드를 작성하는 방법을 알고있는 한이 유휴 시간에 많은 일을 할 수 있습니다.

타임 아웃을 사용하여 원래 문제를 해결할 수 있습니다. GUI 프레임 워크에 지연 후 일부 기능을 호출하도록 지시 한 다음 해당 지연 또는 다른 지연 통화를 재설정합니다.

또 다른 일반적인 질문은 GUI 응용 프로그램에서 네트워크를 통해 통신하는 방법입니다. 네트워크 앱은 GUI 앱과 같습니다. 네트워크 IO 프레임 워크 사용 (예 : 꼬인)는 응용 프로그램의 두 부분을 경쟁적으로 대신 협력 적으로 대기 할 수있게하고 다시 추가 스레드의 필요성을 완화시킬 수 있습니다.

장기 실행 계산은 동기 대신 반복적으로 작성할 수 있으며 GUI가 유휴 상태 인 동안 처리를 수행 할 수 있습니다. 발전기를 사용하여 파이썬 에서이 작업을 아주 쉽게 수행 할 수 있습니다.

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() 생성기 객체에서 생성기가 도달 할 때까지 실행됩니다. yield 또는 return. 당신은 단지 GUI 프레임 워크에 전화하라고 말할 것입니다 long_calculation(some_param, some_callback).next 시간이 있고 결국 콜백이 결과와 함께 호출됩니다.

나는 GTK를 잘 잘 모르겠으므로 어떤 gobject 기능을 호출 해야하는지 말할 수 없습니다. 그러나이 설명을 통해 문서에서 필요한 기능을 찾을 수 있거나 최악의 경우 관련 IRC 채널에 대해 문의하십시오.

불행히도 좋은 일반 사례 답변은 없습니다. 당신이하려는 일을 정확히 명확히한다면, 그 상황에서 스레드가 필요하지 않은 이유를 설명하는 것이 더 쉬울 것입니다.

정지 된 스레드 객체를 다시 시작할 수 없습니다. 시도하지 마십시오. 대신, 객체가 실제로 중지되고 결합 된 후 다시 시작하려면 객체의 새 인스턴스를 만듭니다.

스레드, 유휴 처리 등으로 작업을 정리하는 데 도움이되는 다양한 도구를 사용했습니다.

MAKE_IDLE은 백그라운드에서 협력 적으로 작업을 실행할 수있는 기능 데코레이터입니다. 이것은 UI 스레드에서 한 번 실행하기에 충분히 짧은 짧은 것 사이의 중간 지점이며 앱의 응답성에 영향을 미치지 않고 특수 동기화에서 전체 스레드를 수행하지 않습니다. 장식 된 기능 내부에서 "수율"을 사용하여 처리를 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 ...

아직 끝내지 못했지만 순수하게 유휴 상태에서 순전히 실로 일하는 것을 결합 할 때, 수율이 실행 된 후 다음 섹션이이를 알 수있는 데코레이터 (아직 테스트되지 않았지만 게시되지 않았습니다)가 있습니다. UI의 유휴 시간 또는 스레드에서. 이를 통해 UI 스레드에서 일부 설정을 수행하고 배경 작업을 위해 새 스레드로 전환 한 다음 UI의 유휴 시간으로 전환하여 정리를 수행하여 잠금의 필요성을 최소화 할 수 있습니다.

나는 당신의 코드를 자세히 보지 않았습니다. 그러나 나는 당신의 문제에 대한 두 가지 해결책을 봅니다.

스레드를 전혀 사용하지 마십시오. 대신 다음과 같은 타임 아웃을 사용하십시오.

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 코드가 다음과 같이 지켜줌으로써 동시에 하나의 스레드에서만 호출되는지 확인해야합니다.

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()
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top