2013年12月2日 星期一

PyQt4 教學3 QThread 多線程

多線程python 已有 threading, pyqt 又有 QThread, 一開始都容易有疑問,二者有何不同?應該那一個比較對,我是覺得二者皆可用,網上曾看過有人說,應儘量用 python 本身的 threading, QThread 是原本針對 C++而寫。

任務量較少的情況,我會直接用 threading, 任務量大且需向其它gui 物件傳遞訊息的情況,我會用QThread

這篇是寫PyQt,所以這邊我也只寫 QThread部份

會想使用QThread,我所遇到的情況,是向網頁,抓取資料,又遇網路過慢,延遲過久造成Gui介面等待過久,介面會卡卡、頓頓的。

首先先寫介面,然後點擊 startBtn 可以測試 無線程情況下,圖形介面是不是卡卡的




# -*- coding: UTF8 -*-
import sys
import urllib2
from PyQt4.QtGui import *
from PyQt4.QtCore import *

class Window(QWidget):
    def __init__(self, parent=None):
        super(self.__class__, self).__init__(parent)
        layout = QVBoxLayout()
        hbox = QHBoxLayout()
                                   
        self.edit = QTextEdit()
        self.startBtn = QPushButton("Start")
        self.stopBtn = QPushButton("Stop")
        
        hbox.addStretch()
        hbox.addWidget(self.startBtn)
        hbox.addWidget(self.stopBtn)
        
        layout.addWidget(self.edit)
        layout.addLayout(hbox)
        
        self.setLayout(layout)
    
        # SIGNAL&SLOT
        self.startBtn.clicked.connect(self.test)
                
    def test(self):
        url = 'http://blog.sina.com.cn/'
        html = urllib2.urlopen(url).read()
        length = len(html)
        self.edit.append("Url:%s Content:%s" % (url, length))

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec_())
 
加入線程
 
class Worker(QThread):    
    def __init__(self, parent=None):
        super(self.__class__, self).__init__(parent)
        self.url = 'http://blog.sina.com.cn/'
        
    def run(self):
        html = br.urlopen(self.url).read()
        length = len(html)
        self.emit(SIGNAL("length"), self.url, str(length))
        print 'success'
 
self.emit(SIGNAL("length"), self.url, str(length))
當線程完成任務時,我們請線程發送訊號
"length",並傳遞url及所抓取網頁的資料大小
 
 主程式添加
 
class Window(QWidget):
    def __init__(self, parent=None):
        super(self.__class__, self).__init__(parent)
               .
               .
               . 
        self.work = Worker()
        # SIGNAL&SLOT
        self.startBtn.clicked.connect(self.start)
        self.connect(self.work, SIGNAL("length"), 
                     self.updateUI)
        
    def start(self):
        self.work.start()
    
    def updateUI(self, url, length):
        self.edit.append("Url:%s Content:%s" % (url, length))
 
 
self.connect(self.work, SIGNAL("length"), 
                     self.updateUI)
主程式這邊接收剛線程傳遞出來的資料"length" 

現在我們多添加一點任務給線程
 
import lxml.html as H

 線程改寫成
 
class Worker(QThread):    
    def __init__(self, parent=None):
        super(self.__class__, self).__init__(parent)
        self.url = 'http://blog.sina.com.cn/'
        
    def run(self):
        self.url = 'http://blog.sina.com.cn/'
        html = urllib2.urlopen(self.url).read()
        doc = H.document_fromstring(html)
        urlList = doc.xpath('//a/@href')
        for i in range(10):
            url = urlList[i]
            html = urllib2.urlopen(url).read()
            length = len(html)
            self.emit(SIGNAL("length"), url, str(length))
        print 'success'
 
 
doc = H.document_fromstring(html)
urlList = doc.xpath('//a/@href')
這邊我們用 lxml.html  xpath語法 取得了,所有文章連結
讓線程去跑十篇文章。

在跑文章時候,我們希望避免重複按下startBtn
應修改 
   def start(self):
        self.work.start()
        self.startBtn.setEnabled(False)

在任務一執行時,先關閉
self.startBtn
 
任務完成時,再開啟 self.startBtn
主程式添加以下代碼
        self.connect(self.work, SIGNAL("finished()"), 
                     self.finished)
        
    def finished(self):
        self.startBtn.setEnabled(True)
 
 
 
 
 那如果我們希望執行當中,停止任務呢?
 QMutex() 上鎖吧!
 
最後整個程式代碼如下: 
 
 
# -*- coding: UTF8 -*-
import sys
import lxml.html as H
import urllib2
from PyQt4.QtGui import *
from PyQt4.QtCore import *

class Window(QWidget):
    def __init__(self, parent=None):
        super(self.__class__, self).__init__(parent)
        layout = QVBoxLayout()
        hbox = QHBoxLayout()
                                   
        self.edit = QTextEdit()
        self.startBtn = QPushButton("Start")
        self.stopBtn = QPushButton("Stop")
        
        hbox.addStretch()
        hbox.addWidget(self.startBtn)
        hbox.addWidget(self.stopBtn)
        
        layout.addWidget(self.edit)
        layout.addLayout(hbox)
        
        self.setLayout(layout)
        #
        self.work = Worker()
        # SIGNAL&SLOT
        self.startBtn.clicked.connect(self.start)
        self.stopBtn.clicked.connect(self.stop)
        self.connect(self.work, SIGNAL("length"), 
                     self.updateUI)
        self.connect(self.work, SIGNAL("finished()"), 
                     self.finished)
        
    def start(self):
        self.work.start()
        self.startBtn.setEnabled(False)
        
    def updateUI(self, url, length):
        self.edit.append("Url:%s Content:%s" % (url, length))

    def finished(self):
        self.startBtn.setEnabled(True)
    
    def stop(self):
        self.work.stop()
        self.startBtn.setEnabled(True)

class Worker(QThread):    
    def __init__(self, parent=None):
        super(self.__class__, self).__init__(parent)
        self.stoped = False
        self.mutex = QMutex()
    
    def run(self):
        with QMutexLocker(self.mutex):
            self.stoped = False
        self.url = 'http://blog.sina.com.cn/'
        html = urllib2.urlopen(self.url).read()
        doc = H.document_fromstring(html)
        urlList = doc.xpath('//a/@href')
        for i in range(10):
            url = urlList[i]
            if self.stoped:
                return 
            html = urllib2.urlopen(url).read()
            length = len(html)
            self.emit(SIGNAL("length"), url, str(length))
        print 'success'
    
    def stop(self):
        with QMutexLocker(self.mutex):
            self.stoped = True
    
    def isStop(self):
        with QMutexLocker(self.mutex):
            return self.stoped

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec_())



沒有留言:

張貼留言