Ağır bir eklenti çalıştırırken Qgis'in “yanıt vermiyor” olarak algılanmasını nasıl önleyebilirim?


10

Kullanıcıyı durum hakkında bilgilendirmek için aşağıdaki satırı kullanıyorum:

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

Eklentinin veri kümemde çalışması yaklaşık 2 dakika sürüyor, ancak Windows bunu "yanıt vermiyor" olarak algılıyor ve durum güncellemelerini göstermeyi durduruyor. Yeni bir kullanıcı için, program çöktüğü için bu kadar iyi değil.

Kullanıcı eklentinin durumu ile ilgili karanlıkta kalmamak için herhangi bir çalışma var mı?

Yanıtlar:


14

Nathan W'ın işaret ettiği gibi , bunu yapmanın yolu çok iş parçacıklı olmaktır, ancak QThread'i alt sınıflamak en iyi uygulama değildir. Buraya bakın: http://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/

Aşağıda a'nın nasıl oluşturulacağına ilişkin bir örneğe bakın QObject, sonra onu a'ya taşıyın QThread(yani "doğru" şekilde). Bu örnek, bir vektör katmanındaki tüm özelliklerin toplam alanını hesaplar (yeni QGIS 2.0 API! 'Yı kullanarak).

İlk olarak, bizim için ağır kaldırmayı yapacak "işçi" nesnesini yaratırız:

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)

İşçiyi kullanmak için onu bir vektör katmanıyla başlatmamız, iş parçacığına taşımamız, bazı sinyalleri bağlamamız ve sonra başlatmamız gerekir. Burada neler olduğunu anlamak için yukarıda bağlantılı blog'a bakmak en iyisidir .

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

Bu örnek birkaç önemli noktayı göstermektedir:

  • İşçinin run()yöntemindeki her şey bir dene-hariç ifadesinin içindedir. Kodunuz bir iş parçacığının içinde çöktüğünde kurtarmak zordur. Geri izlemeyi genellikle bağlandığım hata sinyali aracılığıyla yayar QgsMessageLog.
  • Bitmiş sinyal, bağlı yönteme, işlemin başarıyla tamamlanıp tamamlanmadığını ve sonucunu bildirir.
  • İlerleme sinyali, her özellik için bir defa yerine yalnızca tamamlanma yüzdesi değiştiğinde çağrılır. Bu, ilerleme çubuğunu yavaşlatmak için çok fazla çağrı yapılmasını önler, bu da çalışanı başka bir iş parçacığında çalıştırmanın tüm noktasını yener: hesaplamayı kullanıcı arabiriminden ayırmak için.
  • Çalışan kill(), işlevin zarif bir şekilde sonlandırılmasına izin veren bir yöntem uygular . terminate()Yöntemi kullanmaya çalışmayın QThread- kötü şeyler olabilir!

Eklentinizin yapısında threadve workerherhangi bir yerindeki nesneleri izlediğinizden emin olun . Eğer istemezsen kızar. Bunu yapmanın en kolay yolu, bunları oluşturduğunuzda bunları iletişim kutunuzda saklamaktır, örneğin:

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

Veya Qt'nin QThread'in mülkiyetini almasına izin verebilirsiniz:

thread = QtCore.QThread(self)

Bu şablonu bir araya getirmek için tüm dersleri kazmak uzun zaman aldı, ama o zamandan beri her yerde yeniden kullanıyorum.


Teşekkür ederim bu tam olarak ne aradığını ve çok yardımcı oldu! Ben i C # konulara alışkınım ama python bunu düşünmüyordu.
Johan Holtby

Evet bu doğru yol.
Nathan W

1
Bir "ben" olması gerekir. önünde "features = layer.getFeatures ()"? -> "features = self.layer.getFeatures ()"
Håvard Tveite

@ HåvardTveite Haklısınız. Cevaptaki kodu düzelttim.
Snorfalorpagus

Yazdığım bir işlem komut dosyası için bu kalıbı takip etmeye çalışıyorum ve işe yaramakta sorun yaşıyorum. Bu örneği bir komut dosyasına kopyalamaya çalıştım, gerekli içe aktarma ifadelerini ekledim ve worker.progress.connect(self.ui.progressBar)başka bir şeye değiştirdim , ancak her çalıştırdığımda qgis-bin çöküyor. Python kodu veya qgis hata ayıklama deneyimi yok. Tek aldığım Access violation reading location 0x0000000000000008şey bir şey boş gibi görünüyor. Bunu bir işleme komut dosyasında kullanabilmek için eksik olan bazı kurulum kodları var mı?
TJ Rockefeller

4

Bunu yapmanın tek gerçek yolu çoklu kullanımdır.

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

Bazı ekstra okumalar http://joplaete.wordpress.com/2010/07/21/threading-with-pyqt4/

Not Bazı insanlar QThread'den miras almayı sevmez ve görünüşe göre bu bunu yapmanın "doğru" yolu değil, ama işe yarıyor ....


:) Bunu yapmak için kirli bir yol gibi görünüyor. Bazı zamanlar stil gerekli değildir. Bu kez (pyqt içinde ilk) C # için alışkın olduğumdan beri doğru yoldan gideceğimizi düşünüyorum.
Johan Holtby

2
Kirli bir yol değil, bunu yapmanın eski yoluydu.
Nathan W

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.