मैं PyQGIS के साथ क्यूथ्रेड का उपयोग करके एक प्रतिक्रियाशील GUI कैसे बनाए रख सकता हूं


11

मैं QGIS 1.8 के लिए अजगर प्लगइन्स के रूप में कुछ बैच प्रोसेसिंग टूल विकसित कर रहा हूं।

मैंने पाया है कि जब मेरे उपकरण चल रहे हैं तो GUI गैर-प्रतिक्रियाशील हो गया है।

सामान्य ज्ञान यह है कि काम कार्यकर्ता थ्रेड पर किया जाना चाहिए, स्थिति / पूर्ण जानकारी के रूप में संकेतों के रूप में जीयूआई को वापस कर दिया।

मैं के माध्यम से पढ़ा है नदी किनारे गए दस्तावेज़, और doGeometry.py के स्रोत (से एक काम कार्यान्वयन का अध्ययन किया ftools )।

इन स्रोतों का उपयोग करके मैंने एक स्थापित कोड बेस में परिवर्तन करने से पहले इस कार्यक्षमता का पता लगाने के लिए एक सरल कार्यान्वयन बनाने की कोशिश की है।

समग्र संरचना प्लगइन्स मेनू में एक प्रविष्टि है, जो प्रारंभ और स्टॉप बटन के साथ एक संवाद को प्रसारित करती है। बटन एक थ्रेड को नियंत्रित करता है जो 100 तक गिना जाता है, प्रत्येक नंबर के लिए GUI को एक सिग्नल वापस भेज रहा है। जीयूआई प्रत्येक संकेत प्राप्त करता है और एक स्ट्रिंग भेजता है जिसमें नंबर दोनों संदेश लॉग, और विंडो शीर्षक होता है।

इस कार्यान्वयन का कोड यहाँ है:

from PyQt4.QtCore import *
from PyQt4.QtGui import *
from qgis.core import *

class ThreadTest:

    def __init__(self, iface):
        self.iface = iface

    def initGui(self):
        self.action = QAction( u"ThreadTest", self.iface.mainWindow())
        self.action.triggered.connect(self.run)
        self.iface.addPluginToMenu(u"&ThreadTest", self.action)

    def unload(self):
        self.iface.removePluginMenu(u"&ThreadTest",self.action)

    def run(self):
        BusyDialog(self.iface.mainWindow())

class BusyDialog(QDialog):
    def __init__(self, parent):
        QDialog.__init__(self, parent)
        self.parent = parent
        self.setLayout(QVBoxLayout())
        self.startButton = QPushButton("Start", self)
        self.startButton.clicked.connect(self.startButtonHandler)
        self.layout().addWidget(self.startButton)
        self.stopButton=QPushButton("Stop", self)
        self.stopButton.clicked.connect(self.stopButtonHandler)
        self.layout().addWidget(self.stopButton)
        self.show()

    def startButtonHandler(self, toggle):
        self.workerThread = WorkerThread(self.parent)
        QObject.connect( self.workerThread, SIGNAL( "killThread(PyQt_PyObject)" ), \
                                                self.killThread )
        QObject.connect( self.workerThread, SIGNAL( "echoText(PyQt_PyObject)" ), \
                                                self.setText)
        self.workerThread.start(QThread.LowestPriority)
        QgsMessageLog.logMessage("end: startButtonHandler")

    def stopButtonHandler(self, toggle):
        self.killThread()

    def setText(self, text):
        QgsMessageLog.logMessage(str(text))
        self.setWindowTitle(text)

    def killThread(self):
        if self.workerThread.isRunning():
            self.workerThread.exit(0)


class WorkerThread(QThread):
    def __init__(self, parent):
        QThread.__init__(self,parent)

    def run(self):
        self.emit( SIGNAL( "echoText(PyQt_PyObject)" ), "Emit: starting work" )
        self.doLotsOfWork()
        self.emit( SIGNAL( "echoText(PyQt_PyObject)" ), "Emit: finshed work" )
        self.emit( SIGNAL( "killThread(PyQt_PyObject)"), "OK")

    def doLotsOfWork(self):
        count=0
        while count < 100:
            self.emit( SIGNAL( "echoText(PyQt_PyObject)" ), "Emit: " + str(count) )
            count += 1
#           if self.msleep(10):
#               return
#          QThread.yieldCurrentThread()

दुर्भाग्य से यह चुपचाप काम नहीं कर रहा है जैसा कि मुझे उम्मीद थी:

  • खिड़की का शीर्षक काउंटर के साथ "लाइव" अपडेट कर रहा है लेकिन अगर मैं संवाद पर क्लिक करता हूं, तो यह गैर-उत्तरदायी है।
  • काउंटर समाप्त होने तक संदेश लॉग निष्क्रिय है, फिर सभी संदेशों को एक बार में प्रस्तुत करता है। इन संदेशों को QgsMessageLog द्वारा एक टाइम स्टैम्प के साथ टैग किया गया है और इन समय टिकटों से संकेत मिलता है कि उन्हें काउंटर के साथ "लाइव" प्राप्त हुआ था यानी उन्हें कार्यकर्ता धागे या संवाद द्वारा पंक्तिबद्ध नहीं किया जा रहा है।
  • लॉग में संदेशों का क्रम (निम्न प्रकार) इंगित करता है कि स्टार्टबटनहैंडलर कार्यकर्ता थ्रेड कॉमन्स काम करने से पहले पूरा करता है अर्थात थ्रेड एक थ्रेड के रूप में व्यवहार करता है।

    end: startButtonHandler
    Emit: starting work
    Emit: 0
    ...
    Emit: 99
    Emit: finshed work
    
  • ऐसा लगता है कि वर्कर थ्रेड किसी भी संसाधन को GUI थ्रेड के साथ साझा नहीं कर रहा है। उपरोक्त स्रोत के अंत में टिप्पणी की गई कुछ पंक्तियाँ हैं, जहाँ मैंने msleep () और यील्डक्रेन्थ्रेड () को कॉल करने की कोशिश की, लेकिन न तो कोई मदद मिली।

किसी को भी इस अनुभव के साथ किसी को भी मेरी त्रुटि हाजिर करने में सक्षम है? मैं उम्मीद कर रहा हूं कि यह एक सरल लेकिन मौलिक गलती है जिसे पहचानने के बाद इसे ठीक करना आसान है।


क्या यह सामान्य है कि स्टॉप बटन पर क्लिक नहीं किया जा सकता है? उत्तरदायी जीयूआई का मुख्य लक्ष्य प्रक्रिया को रद्द करना है यदि यह बहुत लंबा है। मैं आपकी स्क्रिप्ट को संशोधित करने की कोशिश करता हूं, लेकिन मुझे बटन ठीक से काम नहीं कर सकता है। आप अपने धागे का गर्भपात कैसे करते हैं?
etrimaille

जवाबों:


6

इसलिए मैंने इस समस्या पर एक और नज़र डाली। मैंने स्क्रैच से शुरू किया और सफलता मिली, फिर ऊपर दिए गए कोड को देखने के लिए वापस चला गया और अभी भी इसे ठीक नहीं कर सकता।

इस विषय पर शोध करने वाले किसी व्यक्ति के लिए एक कार्यशील उदाहरण प्रदान करने के हितों में मैं यहां कार्यात्मक कोड प्रदान करूंगा:

from PyQt4.QtCore import *
from PyQt4.QtGui import *

class ThreadManagerDialog(QDialog):
    def __init__( self, iface, title="Worker Thread"):
        QDialog.__init__( self, iface.mainWindow() )
        self.iface = iface
        self.setWindowTitle(title)
        self.setLayout(QVBoxLayout())
        self.primaryLabel = QLabel(self)
        self.layout().addWidget(self.primaryLabel)
        self.primaryBar = QProgressBar(self)
        self.layout().addWidget(self.primaryBar)
        self.secondaryLabel = QLabel(self)
        self.layout().addWidget(self.secondaryLabel)
        self.secondaryBar = QProgressBar(self)
        self.layout().addWidget(self.secondaryBar)
        self.closeButton = QPushButton("Close")
        self.closeButton.setEnabled(False)
        self.layout().addWidget(self.closeButton)
        self.closeButton.clicked.connect(self.reject)
    def run(self):
        self.runThread()
        self.exec_()
    def runThread( self):
        QObject.connect( self.workerThread, SIGNAL( "jobFinished( PyQt_PyObject )" ), self.jobFinishedFromThread )
        QObject.connect( self.workerThread, SIGNAL( "primaryValue( PyQt_PyObject )" ), self.primaryValueFromThread )
        QObject.connect( self.workerThread, SIGNAL( "primaryRange( PyQt_PyObject )" ), self.primaryRangeFromThread )
        QObject.connect( self.workerThread, SIGNAL( "primaryText( PyQt_PyObject )" ), self.primaryTextFromThread )
        QObject.connect( self.workerThread, SIGNAL( "secondaryValue( PyQt_PyObject )" ), self.secondaryValueFromThread )
        QObject.connect( self.workerThread, SIGNAL( "secondaryRange( PyQt_PyObject )" ), self.secondaryRangeFromThread )
        QObject.connect( self.workerThread, SIGNAL( "secondaryText( PyQt_PyObject )" ), self.secondaryTextFromThread )
        self.workerThread.start()
    def cancelThread( self ):
        self.workerThread.stop()
    def jobFinishedFromThread( self, success ):
        self.workerThread.stop()
        self.primaryBar.setValue(self.primaryBar.maximum())
        self.secondaryBar.setValue(self.secondaryBar.maximum())
        self.emit( SIGNAL( "jobFinished( PyQt_PyObject )" ), success )
        self.closeButton.setEnabled( True )
    def primaryValueFromThread( self, value ):
        self.primaryBar.setValue(value)
    def primaryRangeFromThread( self, range_vals ):
        self.primaryBar.setRange( range_vals[ 0 ], range_vals[ 1 ] )
    def primaryTextFromThread( self, value ):
        self.primaryLabel.setText(value)
    def secondaryValueFromThread( self, value ):
        self.secondaryBar.setValue(value)
    def secondaryRangeFromThread( self, range_vals ):
        self.secondaryBar.setRange( range_vals[ 0 ], range_vals[ 1 ] )
    def secondaryTextFromThread( self, value ):
        self.secondaryLabel.setText(value)

class WorkerThread( QThread ):
    def __init__( self, parentThread):
        QThread.__init__( self, parentThread )
    def run( self ):
        self.running = True
        success = self.doWork()
        self.emit( SIGNAL( "jobFinished( PyQt_PyObject )" ), success )
    def stop( self ):
        self.running = False
        pass
    def doWork( self ):
        return True
    def cleanUp( self):
        pass

class CounterThread(WorkerThread):
    def __init__(self, parentThread):
        WorkerThread.__init__(self, parentThread)
    def doWork(self):
        target = 100000000
        stepP= target/100
        stepS=target/10000
        self.emit( SIGNAL( "primaryText( PyQt_PyObject )" ), "Primary" )
        self.emit( SIGNAL( "secondaryText( PyQt_PyObject )" ), "Secondary" )
        self.emit( SIGNAL( "primaryRange( PyQt_PyObject )" ), ( 0, 100 ) )
        self.emit( SIGNAL( "secondaryRange( PyQt_PyObject )" ), ( 0, 100 ) )
        count = 0
        while count < target:
            if count % stepP == 0:
                self.emit( SIGNAL( "primaryValue( PyQt_PyObject )" ), int(count / stepP) )
            if count % stepS == 0:  
                self.emit( SIGNAL( "secondaryValue( PyQt_PyObject )" ), count % stepP / stepS )
            if not self.running:
                return False
            count += 1
        return True

d = ThreadManagerDialog(qgis.utils.iface, "CounterThread Demo")
d.workerThread = CounterThread(qgis.utils.iface.mainWindow())
d.run()

इस नमूने की संरचना एक थ्रेडमैनगरडायलॉग क्लास है जिसे वर्कर थ्रेड (या उपवर्ग) सौंपा जा सकता है। जब संवाद की रन विधि कहा जाता है, तो यह कर्ता पर doWork पद्धति को कॉल करेगा। नतीजा यह है कि doWork में कोई भी कोड एक अलग थ्रेड में चलेगा, उपयोगकर्ता इनपुट का जवाब देने के लिए GUI को मुफ्त छोड़ देगा।

इस नमूने में काउंटरथ्रेड का एक उदाहरण कार्यकर्ता के रूप में सौंपा गया है और एक या दो बार प्रगति पट्टियों को एक या दो मिनट के लिए व्यस्त रखा जाएगा।

नोट: इसे स्वरूपित किया गया है ताकि यह अजगर कंसोल में चिपकाने के लिए तैयार हो। .Py फ़ाइल को सहेजने से पहले अंतिम तीन पंक्तियों को निकालना होगा।


यह एक महान प्लग एंड प्ले उदाहरण है! मैं अपने स्वयं के कामकाजी अल्गोरिथम को लागू करने के लिए इस कोड में सर्वश्रेष्ठ स्थिति के लिए उत्सुक हूं। क्या ऐसे वर्कर को थ्रेड वर्कर में, या फिर क्लास काउंटरह्रेड में, डॉकवर्क को हटाने की आवश्यकता होगी? [इन प्रगति पट्टियों को सम्मिलित कार्यकर्ता एल्गोरिथम (एस) से जोड़ने के हित में पूछा गया
कतल्पा

हाँ, CounterThreadसिर्फ एक नंगे हड्डियों का उदाहरण है बच्चे का वर्ग WorkerThread। यदि आप अधिक सार्थक कार्यान्वयन के साथ अपना बच्चा वर्ग बनाते हैं doWorkतो आपको ठीक होना चाहिए।
केली थॉमस

काउंटरथ्रेड की विशेषताएं मेरे लक्ष्य (प्रगति के उपयोगकर्ता के लिए विस्तृत अधिसूचना) पर लागू होती हैं - लेकिन ऐसे नए c.class 'doWork' रूटीन के साथ कैसे एकीकृत किया जाएगा? (यह भी - प्लेसमेंट वार, काउंटर राइट के भीतर 'doWork'?)
कटप्पा

काउंटरथ्रेड कार्यान्वयन ऊपर a) नौकरी को आरंभीकृत करता है, b) डायलॉग को इनिशियलाइज़ करता है, c) एक कोर लूप करता है, d) सफल समापन पर सही होता है। कोई भी कार्य जिसे लूप के साथ लागू किया जा सकता है उसे बस जगह छोड़ देना चाहिए। एक चेतावनी जो मैं पेश करूंगा, वह यह है कि प्रबंधक के साथ संवाद करने के लिए संकेतों का उत्सर्जन कुछ ओवरहेड के साथ होता है, यानी अगर रैपिड लूप के प्रत्येक पुनरावृत्ति के साथ बुलाया जाए तो यह वास्तविक नौकरी की तुलना में अधिक विलंबता का कारण बन सकता है।
केली थॉमस

सारी सलाह के लिए धन्यवाद। मेरी स्थिति में काम करने के लिए यह परेशानी भरा हो सकता है। वर्तमान में, doWork qgis में एक मिनीडंप दुर्घटना का कारण बनता है। बहुत अधिक भार, या मेरे (नौसिखिए) प्रोग्रामिंग कौशल का एक परिणाम?
कट्टपा
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.