كيفية تتبع تقدم الخيط في بيثون دون تجميد pyqt واجهة المستخدم الرسومية؟

StackOverflow https://stackoverflow.com/questions/569650

سؤال

أسئلة:

  1. ما هي أفضل الممارسات لتتبع تقدم فقي دون قفل واجهة المستخدم الرسومية ("لا يستجيب")؟
  2. عموما، ما هي أفضل الممارسات للخيوط كما ينطبق على تطوير واجهة المستخدم الرسومية؟

خلفية سؤال:

  • لدي Gui pyqt لنظام التشغيل Windows.
  • يتم استخدامه لمعالجة مجموعات مستندات HTML.
  • يستغرق أي مكان من ثلاث ثوان إلى ثلاث ساعات لمعالجة مجموعة من المستندات.
  • أريد أن أكون قادرا على معالجة مجموعات متعددة في نفس الوقت.
  • لا أريد قفل واجهة المستخدم الرسومية.
  • أنا أبحث في وحدة الخيوط لتحقيق ذلك.
  • أنا جديد نسبيا في الخيوط.
  • واجهة المستخدم الرسومية لديها شريط التقدم واحد.
  • أريدها أن تعرض تقدم الخيط المحدد.
  • عرض نتائج الخيط المحدد إذا انتهى.
  • أنا أستخدم بيثون 2.5.

فكرتي: احصل على مؤشرات الترابط تنبعث منها qtsignal عند تحديث التقدم الذي يؤدي إلى تشغيل بعض الوظائف التي تقوم بتحديث شريط التقدم. إشارة أيضا عند الانتهاء من المعالجة حتى يمكن عرض النتائج.

#NOTE: this is example code for my idea, you do not have
#      to read this to answer the question(s).

import threading
from PyQt4 import QtCore, QtGui
import re
import copy

class ProcessingThread(threading.Thread, QtCore.QObject):

    __pyqtSignals__ = ( "progressUpdated(str)",
                        "resultsReady(str)")

    def __init__(self, docs):
        self.docs = docs
        self.progress = 0   #int between 0 and 100
        self.results = []
        threading.Thread.__init__(self)

    def getResults(self):
        return copy.deepcopy(self.results)

    def run(self):
        num_docs = len(self.docs) - 1
        for i, doc in enumerate(self.docs):
            processed_doc = self.processDoc(doc)
            self.results.append(processed_doc)
            new_progress = int((float(i)/num_docs)*100)

            #emit signal only if progress has changed
            if self.progress != new_progress:
                self.emit(QtCore.SIGNAL("progressUpdated(str)"), self.getName())
            self.progress = new_progress
            if self.progress == 100:
                self.emit(QtCore.SIGNAL("resultsReady(str)"), self.getName())

    def processDoc(self, doc):
        ''' this is tivial for shortness sake '''
        return re.findall('<a [^>]*>.*?</a>', doc)


class GuiApp(QtGui.QMainWindow):

    def __init__(self):
        self.processing_threads = {}  #{'thread_name': Thread(processing_thread)}
        self.progress_object = {}     #{'thread_name': int(thread_progress)}
        self.results_object = {}      #{'thread_name': []}
        self.selected_thread = ''     #'thread_name'

    def processDocs(self, docs):
        #create new thread
        p_thread = ProcessingThread(docs)
        thread_name = "example_thread_name"
        p_thread.setName(thread_name)
        p_thread.start()

        #add thread to dict of threads
        self.processing_threads[thread_name] = p_thread

        #init progress_object for this thread
        self.progress_object[thread_name] = p_thread.progress  

        #connect thread signals to GuiApp functions
        QtCore.QObject.connect(p_thread, QtCore.SIGNAL('progressUpdated(str)'), self.updateProgressObject(thread_name))
        QtCore.QObject.connect(p_thread, QtCore.SIGNAL('resultsReady(str)'), self.updateResultsObject(thread_name))

    def updateProgressObject(self, thread_name):
        #update progress_object for all threads
        self.progress_object[thread_name] = self.processing_threads[thread_name].progress

        #update progress bar for selected thread
        if self.selected_thread == thread_name:
            self.setProgressBar(self.progress_object[self.selected_thread])

    def updateResultsObject(self, thread_name):
        #update results_object for thread with results
        self.results_object[thread_name] = self.processing_threads[thread_name].getResults()

        #update results widget for selected thread
        try:
            self.setResultsWidget(self.results_object[thread_name])
        except KeyError:
            self.setResultsWidget(None)

سيكون موضع تقدير أي تعليق على هذا النهج (مثل العيوب والمخاطر والوظائف وما إلى ذلك).

الدقة:

انتهى بي الأمر باستخدام فئة Qthead والشارات المرتبطة بالاتصال بين المواضيع. هذا هو في المقام الأول لأن برنامجي يستخدم بالفعل QT / PYQT4 لكائنات / الحاجيات GUI. يتطلب هذا الحل أيضا تغييرات أقل في التعليمات البرمجية الحالية لتنفيذها.

فيما يلي رابط مقالة كيو تي قابلة للتطبيق يشرح كيف تعالج QT المواضيع والإشارات، http://www.linuxjournal.com/article/9602.. وبعد مقتطف أدناه:

لحسن الحظ، يسمح QT الإشارات والفخات المراد توصيلها عبر المواضيع - طالما أن مؤشرات الترابط تدير حلقات الحدث الخاصة بهم. هذه طريقة للتواصل منظفة بكثير مقارنة بإرسال الأحداث واستقبالها، لأنها تتجنب كل فئات مسك الدفاتر والفئات المشتقة من QEVENT التي تصبح ضرورية في أي تطبيق غير متقطع. يصبح التواصل بين المواضيع الآن مسألة توصيل الإشارات من مؤشر ترابط واحد إلى فتحات أخرى، ويتم التعامل مع مشكلات MuteXing وسلامة الخيوط الخاصة بتبادل البيانات بين المواضيع بواسطة QT.

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

ملاحظة: ايبين لديها أيضا إجابة جيدة، وإذا لم تكن تستخدم PYQT4، والتي تعالج سلامة الخيوط والمواد التشخيص، كان حله كان خياري.

هل كانت مفيدة؟

المحلول

إذا كنت ترغب في استخدام الإشارات للإشارة إلى التقدم المحرز إلى مؤشر الترابط الرئيسي، فعليك استخدام فئة Qthead في Pyqt بدلا من فئة مؤشر الترابط من وحدة خيوط Python.

مثال بسيط يستخدم Qthead والإشارات والفتحات يمكن العثور عليها على Wiki Pyqt:

https://wiki.python.org/moin/pyqt/threading ،_signals_and_slots.

نصائح أخرى

لن تعمل قوائم انتظار Python الأصلية لأنك تضطر إلى حظر Continue Get ()، والتي تنتج UI الخاص بك.

تنفذ QT بشكل أساسي نظام في انتظار في الداخل للاتصال عبر الخيوط المتقاطعة. جرب هذه الاتصال من أي مؤشر ترابط لنشر مكالمة إلى فتحة.

qtcore.qmetaobject.invokemethod ()

إنها clunky وتوثيقها بشكل سيء، لكن يجب أن تفعل ما تريد حتى من مؤشر ترابط غير QT.

يمكنك أيضا استخدام آلات الأحداث لهذا. انظر Qapplication (أو Qcoreapplication) لطريقة تسمى شيئا مثل "المشاركة".

تحرير: إليك مثال أكثر اكتمالا ...

قمت بإنشاء صفي الخاص بناء على Qwidget. لديها فتحة تقبل سلسلة؛ أعرفه مثل هذا:

@QtCore.pyqtSlot(str)
def add_text(self, text):
   ...

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

QtCore.QMetaObject.invokeMethod(mywidget, "add_text", QtCore.Q_ARG(str,"hello world"))

clunky، ولكن يحصل لك هناك.

دان.

أوصي بك لاستخدام قائمة الانتظار بدلا من الإشارات. أنا شخصيا أجد طريقة أكثر قوة ومفهومة للبرمجة، لأنها أكثر متزامنة.

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

بهذه الطريقة، يمكن أيضا استخدام "قائمة انتظار الوظائف" الواحدة "و" قائمة انتظار النتائج "من قبل مجموعة من مؤشرات الترابط العامل، فهي تسجل جميع المعلومات من المواضيع في مؤشر ترابط GUI الرئيسي.

إذا لم يغير طريقة "ColoryDoc" الطريقة الخاصة بك أي بيانات أخرى (تبحث فقط عن بعض البيانات وإرجاعها ولا تغير المتغيرات أو خصائص فئة الوالدين)، فيمكنك استخدام ماكرواد Py_begin_allow_threads و py_end_allow_threads ( انظر هنا للحصول على التفاصيل ) فيه. لذلك سيتم معالجة المستند في مؤشر ترابط لن قفل المترجم الخاص وسيتم تحديث واجهة المستخدم.

أنت دائما ستكون هذه المشكلة في بيثون. Google Gil "قفل Global Formetor" لمزيد من الخلفية. هناك طريقتان موصى به عموما للالتفاف حول المشكلة التي تعاني منها: استخدام ملتوية, أو استخدام وحدة مماثلة ل multiprocessing. وحدة قدمت في 2.5.

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

ستسخر وحدة المعالجة المتعددة عملية جديدة وتستخدم IPC لجعلها تتصرف كما لو كان لديك خيوط حقيقية. الجانب السلبي الوحيد هو أنك ستحتاج إلى تثبيت Python 2.5 وهي جديدة إلى حد ما وتضمن IND "في معظم Distros Linux أو OSX بشكل افتراضي.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top