مبتدئ المستوى الثعبان خيوط المشاكل
-
20-08-2019 - |
سؤال
كما شخص جديد إلى تطوير واجهة المستخدم الرسومية في بايثون (مع 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()