سؤال

كما شخص جديد إلى تطوير واجهة المستخدم الرسومية في بايثون (مع pyGTK), لقد بدأت للتو تعلم عن خيوط.لاختبار مهاراتي كتبت قليلا بسيطة واجهة GTK مع زر تشغيل/إيقاف.والهدف من ذلك هو أنه عندما يتم النقر عليها ، موضوع يبدأ بسرعة الزيادات رقم في مربع النص ، مع الحفاظ على واجهة المستخدم الرسومية استجابة.

لدي المستخدم الرسومية تعمل على ما يرام, ولكن أواجه مشاكل مع خيوط.وربما هو مشكلة بسيطة, لكن عقلي عن المقلي لهذا اليوم.أدناه لقد لصق أول تعقيب من مترجم بايثون ، تليها رمز.يمكنك الذهاب إلى http://drop.io/pxgr5id لتحميل البرنامج.أنا باستخدام bzr للمراجعة التحكم, لذلك إذا كنت تريد أن تجعل من تعديل وإعادة ألقه ، يرجى تطبيق التغييرات.أنا أيضا لصق التعليمات البرمجية في http://dpaste.com/113388/ لأنه يمكن أن يكون خط الأعداد و هذا تخفيض السعر الاشياء يسبب لي الصداع.

تحديث 27 كانون الثاني / يناير, 15:52 بتوقيت شرق الولايات المتحدة:قليلا تحديث رمز يمكن العثور عليها هنا: http://drop.io/threagui/asset/thread-gui-rev3-tar-gz

Traceback

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 هي صعبة بعض الشيء إذا كنت تريد أن تفعل ذلك الحق.في الأساس يجب أن يتم تحديث واجهة المستخدم الرسومية من داخل أي موضوع من الترابط الرئيسي (مشترك القيد في واجهة المستخدم الرسومية يبس).وعادة ما يتم ذلك في PyGTK باستخدام آلية قائمة الانتظار الرسائل (التواصل بين العمال و المستخدم الرسومية) والتي تقرأ بشكل دوري باستخدام وظيفة مهلة.مرة واحدة كنت قد عرض على العروة المحلية حول هذا الموضوع ، يمكنك الاستيلاء على سبيل المثال رمز هذا العرض من جوجل كود مستودع.إلقاء نظرة على 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

بهذه الطريقة, تطبيق التحديثات واجهة المستخدم الرسومية متى "الخمول" (قبل جتك يعني) مما تسبب لا يتجمد.

  • 1:إنشاء مؤشر ترابط
  • 2:إنشاء الخمول الموقت
  • 3:daemonize الخيط وبالتالي فإن التطبيق يمكن أن تكون مغلقة دون انتظار الانتهاء الموضوع
  • 4:بداية الموضوع

نصائح أخرى

عموما من الأفضل تجنب المواضيع عندما يمكنك.من الصعب جدا أن أكتب الخيوط التطبيق بشكل صحيح و حتى أكثر من الصعب أن تعرف أنك حصلت على ذلك الحق.منذ كنت تكتب تطبيق واجهة المستخدم الرسومية, أنه من الأسهل بالنسبة لك أن تصور كيفية القيام بذلك منذ كنت بالفعل أن أكتب التطبيق الخاص بك في غضون غير متزامن الإطار.

الشيء المهم أن ندرك أن تطبيق واجهة المستخدم الرسومية تفعل الكثير من أي شيء.أنه يقضي معظم وقته في انتظار نظام التشغيل أن نقول أن شيئا ما قد حدث.يمكنك أن تفعل الكثير من الأشياء في هذا الوقت الضائع طالما كنت تعرف كيفية كتابة طويلة-تشغيل التعليمات البرمجية بحيث لا كتلة.

يمكنك حل المشكلة الأصلية باستخدام مهلة;إخبار المستخدم الرسومية إطار للاتصال مرة أخرى بعض من وظيفة بعد تأخير ، ومن ثم إعادة هذا التأخير أو بدء آخر تأخر الدعوة.

سؤال شائع آخر هو كيفية التواصل عبر الشبكة في تطبيق واجهة المستخدم الرسومية.شبكة التطبيقات مثل تطبيقات واجهة المستخدم الرسومية في أن يفعلوا الكثير من الانتظار.باستخدام شبكة IO إطار (مثل الملتوية) يجعل من السهل أن يكون كل أجزاء من التطبيق الخاص بك الانتظار تعاوني بدلا من تنافسية, و مرة أخرى يخفف من الحاجة إلى المزيد من المواضيع.

طويلة-تشغيل العمليات الحسابية يمكن أن تكون مكتوبة تكرارا بدلا من بشكل متزامن ، ويمكنك أن تفعل معالجة الخاص بك في حين أن واجهة المستخدم الرسومية خاملا.يمكنك استخدام مولد القيام بذلك بسهولة في بيثون.

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.عليك أن واجهة المستخدم الرسومية إطار الاتصال long_calculation(some_param, some_callback).next عندما يكون لديه الوقت و في نهاية المطاف الخاص بك الاستدعاء سوف يطلق مع النتيجة.

أنا لا أعرف جتك جيدا, لذا لا أستطيع أن أقول لك أي gobject الوظائف يجب أن تكون الدعوة.مع هذا التفسير ، على الرغم من أنك يجب أن تكون قادرة على العثور على الوظائف اللازمة في الوثائق ، أو في أسوأ الأحوال ، اسأل على ذات المركز القناة.

للأسف لا توجد العامة جيدة-حالة الإجابة.إذا كان لك أن توضح بالضبط ما كنت تحاول القيام به ، فإنه سيكون من الأسهل لشرح لماذا أنت لا تحتاج إلى المواضيع في هذا الوضع.

لا يمكنك تشغيل توقفت موضوع الكائن ؛ لا تحاول.بدلا من إنشاء مثيل جديد من الكائن إذا كنت ترغب في إعادة تشغيل بعد انها حقا يتوقف وانضم.

لقد لعبت مع أدوات مختلفة للمساعدة في تنظيف العمل مع خيوط معالجة الخمول ، إلخ.

make_idle هي وظيفة الديكور الذي يسمح لك لتشغيل مهمة في الخلفية بشكل تعاوني.هذا هو الوسط بين شيء قصير بما يكفي لتشغيل مرة واحدة في 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 ، والتحول إلى موضوع جديد لعمل خلفية الاشياء ، ومن ثم التبديل إلى واجهة المستخدم الخمول الوقت للقيام تنظيف تقليل الحاجة الأقفال.

أنا لم ينظر في التفاصيل على التعليمات البرمجية الخاصة بك.ولكن أرى حلين لمشكلتك:

لا تستخدم في جميع المواضيع.بدلا من استخدام مهلة مثل هذا:

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()

عند استخدام المواضيع يجب التأكد من أن المستخدم الرسومية هو رمز فقط اتصل من موضوع واحد في نفس الوقت من قبل حراسة عليه مثل هذا:

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