ฉันจะป้องกัน Qgis ไม่ให้ตรวจพบว่าเป็น "ไม่ตอบสนอง" เมื่อใช้งานปลั๊กอินขนาดใหญ่ได้อย่างไร


10

ฉันใช้สาย folowing เพื่อแจ้งผู้ใช้เกี่ยวกับสถานะ:

iface.mainWindow().statusBar().showMessage("Status:" + str(i))

ปลั๊กอินใช้เวลาประมาณ 2 นาทีในการรันบนชุดข้อมูลของฉัน แต่ windows ตรวจพบว่าเป็น "ไม่ตอบสนอง" และหยุดแสดงการอัปเดตสถานะ สำหรับผู้ใช้ใหม่มันไม่ค่อยดีนักเนื่องจากดูเหมือนว่าโปรแกรมจะพัง

มีวิธีแก้ไขหรือไม่เพื่อให้ผู้ใช้ไม่ถูกทิ้งในที่มืดเกี่ยวกับสถานะของปลั๊กอิน?

คำตอบ:


13

เมื่อNathan Wชี้ให้เห็นวิธีการทำเช่นนี้คือการมัลติเธรด แต่การทำคลาสย่อย QThread ไม่ใช่วิธีปฏิบัติที่ดีที่สุด ดูที่นี่: http://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/

ดูตัวอย่างวิธีสร้าง a QObjectจากนั้นย้ายไปยัง a QThread(เช่นวิธี "ถูกต้อง" เพื่อทำ) ตัวอย่างนี้คำนวณพื้นที่ทั้งหมดของฟีเจอร์ทั้งหมดในเวกเตอร์เลเยอร์ (ใช้ QGIS 2.0 API ใหม่!)

อันดับแรกเราสร้างวัตถุ "ผู้ปฏิบัติงาน" ที่จะยกของหนักสำหรับเรา:

class Worker(QtCore.QObject):
    def __init__(self, layer, *args, **kwargs):
        QtCore.QObject.__init__(self, *args, **kwargs)
        self.layer = layer
        self.total_area = 0.0
        self.processed = 0
        self.percentage = 0
        self.abort = False

    def run(self):
        try:
            self.status.emit('Task started!')
            self.feature_count = self.layer.featureCount()
            features = self.layer.getFeatures()
            for feature in features:
                if self.abort is True:
                    self.killed.emit()
                    break
                geom = feature.geometry()
                self.total_area += geom.area()
                self.calculate_progress()
            self.status.emit('Task finished!')
        except:
            import traceback
            self.error.emit(traceback.format_exc())
            self.finished.emit(False, self.total_area)
        else:
            self.finished.emit(True, self.total_area)

    def calculate_progress(self):
        self.processed = self.processed + 1
        percentage_new = (self.processed * 100) / self.feature_count
        if percentage_new > self.percentage:
            self.percentage = percentage_new
            self.progress.emit(self.percentage)

    def kill(self):
        self.abort = True

    progress = QtCore.pyqtSignal(int)
    status = QtCore.pyqtSignal(str)
    error = QtCore.pyqtSignal(str)
    killed = QtCore.pyqtSignal()
    finished = QtCore.pyqtSignal(bool, float)

ในการใช้ผู้ปฏิบัติงานเราต้อง initalise มันด้วยเวกเตอร์เลเยอร์, ​​ย้ายไปที่เธรด, เชื่อมต่อสัญญาณบางอย่าง, จากนั้นเริ่มมัน. อาจเป็นการดีที่สุดที่จะดูบล็อกที่ลิงก์ด้านบนเพื่อทำความเข้าใจว่าเกิดอะไรขึ้นที่นี่

thread = QtCore.QThread()
worker = Worker(layer)
worker.moveToThread(thread)
thread.started.connect(worker.run)
worker.progress.connect(self.ui.progressBar)
worker.status.connect(iface.mainWindow().statusBar().showMessage)
worker.finished.connect(worker.deleteLater)
thread.finished.connect(thread.deleteLater)
worker.finished.connect(thread.quit)
thread.start()

ตัวอย่างนี้แสดงประเด็นสำคัญบางประการ:

  • ทุกอย่างภายในrun()วิธีการของผู้ปฏิบัติงานอยู่ในคำสั่งลองยกเว้น เป็นการยากที่จะกู้คืนเมื่อรหัสของคุณขัดข้องภายในเธรด มันส่งเสียง traceback QgsMessageLogผ่านสัญญาณข้อผิดพลาดที่ฉันมักจะเชื่อมต่อกับที่
  • สัญญาณเสร็จแล้วจะบอกวิธีการเชื่อมต่อหากกระบวนการเสร็จสมบูรณ์เป็นผลสำเร็จ
  • สัญญาณความคืบหน้าจะถูกเรียกใช้เมื่อเปอร์เซ็นต์การเปลี่ยนแปลงเสร็จสมบูรณ์แทนที่จะเป็นหนึ่งครั้งสำหรับทุกคุณสมบัติ สิ่งนี้ป้องกันไม่ให้มีการเรียกจำนวนมากเกินไปในการอัปเดตแถบความคืบหน้าซึ่งทำให้กระบวนการของผู้ปฏิบัติงานช้าลงซึ่งจะทำลายจุดทั้งหมดของการเรียกใช้งานในเธรดอื่น: เพื่อแยกการคำนวณออกจากส่วนติดต่อผู้ใช้
  • ผู้ปฏิบัติงานใช้kill()วิธีการที่อนุญาตให้ฟังก์ชั่นยุติอย่างสง่างาม อย่าพยายามใช้terminate()วิธีการในQThread- สิ่งเลวร้ายอาจเกิดขึ้นได้!

อย่าลืมติดตามthreadและworkerวัตถุในโครงสร้างปลั๊กอินของคุณ Qt โกรธถ้าคุณทำไม่ได้ วิธีที่ง่ายที่สุดในการทำเช่นนี้คือเก็บไว้ในกล่องโต้ตอบเมื่อคุณสร้างขึ้นเช่น:

thread = self.thread = QtCore.QThread()
worker = self.worker = Worker(layer)

หรือคุณสามารถให้ Qt เป็นเจ้าของ QThread:

thread = QtCore.QThread(self)

ฉันใช้เวลานานในการขุดบทเรียนทั้งหมดเพื่อที่จะนำเทมเพลตนี้มารวมกัน แต่ตั้งแต่นั้นมาฉันก็นำมันกลับมาใช้ใหม่ทุกที่


ขอบคุณนี่คือสิ่งที่ฉันกำลังมองหาและมันมีประโยชน์มาก! ฉันคุ้นเคยกับเธรด i C # แต่ไม่ได้คิดในภาษาไพ ธ อน
Johan Holtby

ใช่นี่เป็นวิธีที่ถูกต้อง
Nathan W

1
ควรจะมี "ตัวเอง" ด้านหน้าของเลเยอร์ใน "features = layer.getFeatures ()"? -> "features = self.layer.getFeatures ()"
Håvard Tveite

@ HåvardTveiteคุณถูกต้อง ฉันแก้ไขรหัสในคำตอบ
Snorfalorpagus

ฉันพยายามติดตามรูปแบบนี้เพื่อประมวลผลสคริปต์ที่ฉันเขียนและฉันมีปัญหาในการทำให้ใช้งานได้ ฉันพยายามคัดลอกตัวอย่างนี้ไปยังไฟล์สคริปต์เพิ่มคำสั่งการนำเข้าที่จำเป็นและเปลี่ยนworker.progress.connect(self.ui.progressBar)เป็นอย่างอื่น แต่ทุกครั้งที่ฉันเรียกใช้ qgis-bin จะล้มเหลว ฉันไม่มีประสบการณ์ในการดีบักรหัสหลามหรือ qgis ทั้งหมดที่ฉันได้รับคือAccess violation reading location 0x0000000000000008ดังนั้นมันจึงดูเหมือนว่าบางสิ่งบางอย่างเป็นโมฆะ มีรหัสการตั้งค่าบางอย่างที่ขาดหายไปเพื่อใช้ในสคริปต์การประมวลผลหรือไม่?
TJ Rockefeller

4

วิธีการที่แท้จริงเพียงอย่างเดียวของคุณคือการทำมัลติเธรด

class MyLongRunningStuff(QThread):
    progressReport = pyqtSignal(str)
    def __init__(self):
       QThread.__init__(self)

    def run(self):
       # do your long runnning thing
       self.progressReport.emit("I just did X")

 thread = MyLongRunningStuff()
 thread.progressReport.connect(self.updatetheuimethod)
 thread.start()

อ่านเพิ่มเติมพิเศษhttp://joplaete.wordpress.com/2010/07/21/threading-with-pyqt4/

หมายเหตุบางคนไม่ชอบการสืบทอดจาก QThread และเห็นได้ชัดว่านี่ไม่ใช่วิธีที่ "ถูกต้อง" ที่จะทำ แต่มันทำงานได้ ....


:) ดูเหมือนจะเป็นวิธีที่สกปรกมากที่จะทำ สไตล์บางครั้งไม่จำเป็น สำหรับครั้งนี้ (ครั้งแรกใน pyqt) ฉันคิดว่าฉันจะไปด้วยวิธีที่ถูกต้องตั้งแต่ฉันคุ้นเคยกับที่ใน C #
Johan Holtby

2
มันไม่ใช่วิธีที่สกปรกมันเป็นวิธีการทำแบบเดิม
Nathan W

2

เนื่องจากคำถามนี้ค่อนข้างเก่าจึงสมควรได้รับการอัปเดต ด้วย QGIS 3 มีวิธีการกับ QgsTask.fromFunction (), QgsProcessingAlgRunnerTask () และ QgsApplication.taskManager (). addTask ()

ข้อมูลเพิ่มเติมเกี่ยวกับมันที่การใช้เธรดใน PyQGIS3 โดย MARCO BERNASOCCHI

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.