一半君的总结纸

听话只听一半君

伪”多”线程QThread的中古(subclass QThread)和近代(moveToThread)使用方法

备忘录

下面的例子里的方法1是Jo Plaete的,稍微修改,方法2是在1的基础上加上SO大神的回复

  • 古典方法,不用thread,会卡死

    qthread_1

    import sys
    import time
    from PyQt4 import QtCore, QtGui
    
    
    class MyApp(QtGui.QWidget):
    
        def __init__(self, parent=None):
            QtGui.QWidget.__init__(self, parent)
    
            self.setGeometry(300, 300, 280, 600)
            self.setWindowTitle('non threads')
    
            self.layout = QtGui.QVBoxLayout(self)
    
            self.testButton = QtGui.QPushButton("_non_thread")
            self.testButton.released.connect(self.test)
            self.listwidget = QtGui.QListWidget(self)
    
            self.layout.addWidget(self.testButton)
            self.layout.addWidget(self.listwidget)
    
        def add(self, text):
            """ Add item to list widget """
            print "Add: " + text
            self.listwidget.addItem(text)
            # self.listwidget.sortItems()
    
        def addBatch(self, text="test", iters=6, delay=0.3):
            """ Add several items to list widget """
            for i in range(iters):
                time.sleep(delay)  # artificial time delay
                self.add(text+" "+str(i))
    
        def test(self):
            # self.listwidget.clear()
            # adding entries just from main application: locks ui
            self.addBatch("_non_thread", iters=6, delay=0.3)
    
    # run
    app = QtGui.QApplication(sys.argv)
    test = MyApp()
    test.show()
    app.exec_()
    
    
  • 方法1:中古时代法,subclass QThread然后reimplement run method,关于这种方法正确与否,答案在这里(最后结论是视情况而定

    qthread_2

    import sys
    import time
    from PyQt4 import QtCore, QtGui
    from PyQt4.QtCore import pyqtSignal
    
    
    class MyApp(QtGui.QWidget):
    
        def __init__(self, parent=None):
            QtGui.QWidget.__init__(self, parent)
    
            self.setGeometry(300, 300, 280, 600)
            self.setWindowTitle('using threads')
    
            self.layout = QtGui.QVBoxLayout(self)
    
            self.testButton = QtGui.QPushButton("QThread")
            self.testButton.released.connect(self.test)
            self.listwidget = QtGui.QListWidget(self)
    
            self.layout.addWidget(self.testButton)
            self.layout.addWidget(self.listwidget)
    
            self.threadPool = []
    
        def add(self, text):
            """ Add item to list widget """
            print "Add: " + text
            self.listwidget.addItem(text)
            self.listwidget.sortItems()
    
        def addBatch(self, text="test", iters=6, delay=0.3):
            """ Add several items to list widget """
            for i in range(iters):
                time.sleep(delay)  # artificial time delay
                self.add(text+" "+str(i))
    
        def test(self):
            # adding by emitting signal in different thread
            self.threadPool.append(WorkThread())
            self.threadPool[-1].update.connect(self.add)
            self.threadPool[-1].start()
    
    
    class WorkThread(QtCore.QThread):
    
        update = pyqtSignal(str)
    
        def __init__(self):
            QtCore.QThread.__init__(self)
    
        def __del__(self):
            self.wait()
    
        def run(self):
            for i in range(6):
                time.sleep(0.3)  # artificial time delay
                self.update.emit("from work thread " + str(i))
            return
    
    
    # run
    app = QtGui.QApplication(sys.argv)
    test = MyApp()
    test.show()
    app.exec_()
    
    
    
  • 方法1改进版:使用GenericThread,这样的好处是可以把逻辑部分放在main class里,只把一个function传给这个QThread class

    qthread_generic

    import sys
    import time
    from PyQt4 import QtCore, QtGui
    from PyQt4.QtCore import pyqtSignal
    
    class MyApp(QtGui.QWidget):
    
        def __init__(self, parent=None):
            QtGui.QWidget.__init__(self, parent)
    
            self.setGeometry(300, 300, 350, 600)
            self.setWindowTitle('using threads')
    
            self.layout = QtGui.QVBoxLayout(self)
    
            self.testButton = QtGui.QPushButton("QThread")
            self.testButton.released.connect(self.test)
            self.listwidget = QtGui.QListWidget(self)
    
            self.layout.addWidget(self.testButton)
            self.layout.addWidget(self.listwidget)
    
            self.threadPool = []
    
        def add(self, text):
            """ Add item to list widget """
            print "Add: " + text
            self.listwidget.addItem(text)
            self.listwidget.sortItems()
    
        def addBatch(self, text="test", iters=6, delay=0.3):
            """ Add several items to list widget """
            for i in range(iters):
                time.sleep(delay)  # artificial time delay
                self.emit(QtCore.SIGNAL('add(QString)'), text+" "+str(i))
    
        def test(self):
            # generic thread using signal
            self.genericThread = GenericThread(
                self.addBatch, "from generic thread using signal ", delay=0.3)
    
            self.connect(self, QtCore.SIGNAL("add(QString)"), self.add)
            self.genericThread.start()
    
    
    class GenericThread(QtCore.QThread):
    
        def __init__(self, function, *args, **kwargs):
            QtCore.QThread.__init__(self)
            self.function = function
            self.args = args
            self.kwargs = kwargs
    
        def __del__(self):
            self.wait()
    
        def run(self):
            self.function(*self.args, **self.kwargs)
            return
    # run
    app = QtGui.QApplication(sys.argv)
    test = MyApp()
    test.show()
    app.exec_()
    
    
  • 方法2: 使用movetothread
    import sys
    import time
    from PyQt4 import QtCore, QtGui
    
    
    class MyApp(QtGui.QWidget):
    
        def __init__(self, parent=None):
            QtGui.QWidget.__init__(self, parent)
    
            self.setGeometry(300, 300, 280, 600)
            self.setWindowTitle('using threads')
    
            self.layout = QtGui.QVBoxLayout(self)
    
            self.testButton = QtGui.QPushButton("QThread")
            self.testButton.released.connect(self.test)
            self.listwidget = QtGui.QListWidget(self)
    
            self.layout.addWidget(self.testButton)
            self.layout.addWidget(self.listwidget)
    
        def add(self, text):
            """ Add item to list widget """
            print "Add: " + text
            self.listwidget.addItem(text)
            self.listwidget.sortItems()
    
        def addBatch(self, text="test", iters=6, delay=0.3):
            """ Add several items to list widget """
            for i in range(iters):
                time.sleep(delay)  # artificial time delay
                self.add(text+" "+str(i))
    
        def test(self):
            objThread = QtCore.QThread()
            worker = Worker()
            worker.moveToThread(objThread)
    
            worker.update.connect(self.add)
            objThread.started.connect(worker.longRunning)
            # objThread.finished.connect(self.xxx)
            objThread.start()
    
            self.objThread = objThread
            self.worker = worker
    
    
    class Worker(QtCore.QObject):
    
        update = QtCore.pyqtSignal(str)
    
        def longRunning(self):
            count = 0
            while count < 5:
                time.sleep(0.5)
    
                print "Increasing", count
                count += 1
                self.update.emit('Increasing %s' % count)
    
    # run
    app = QtGui.QApplication(sys.argv)
    test = MyApp()
    test.show()
    app.exec_()
    
    
    
  • 方法2改进版:
    import sys
    import time
    from PyQt4 import QtCore, QtGui
    from PyQt4.QtCore import pyqtSignal, pyqtSlot
    
    
    class MyApp(QtGui.QWidget):
    
        def __init__(self, parent=None):
            QtGui.QWidget.__init__(self, parent)
    
            self.setGeometry(300, 300, 280, 600)
            self.setWindowTitle('using threads')
    
            self.layout = QtGui.QVBoxLayout(self)
    
            self.testButton = QtGui.QPushButton("QThread")
            self.testButton.released.connect(self.test)
            self.listwidget = QtGui.QListWidget(self)
    
            self.layout.addWidget(self.testButton)
            self.layout.addWidget(self.listwidget)
    
            self.threadPool = []
    
        def add(self, text):
            """ Add item to list widget """
            print "Add: " + text
            self.listwidget.addItem(text)
            self.listwidget.sortItems()
    
        def addBatch(self, text="test", iters=6, delay=0.3):
            """ Add several items to list widget """
            for i in range(iters):
                time.sleep(delay)  # artificial time delay
                self.add(text+" "+str(i))
    
        def test(self):
            my_thread = QtCore.QThread()
            my_thread.start()
    
            # This causes my_worker.run() to eventually execute in my_thread:
            my_worker = GenericWorker(self.addBatch)
            my_worker.moveToThread(my_thread)
            my_worker.start.emit("hello")
    
            self.threadPool.append(my_thread)
            self.my_worker = my_worker
    
    
    class GenericWorker(QtCore.QObject):
    
        start = pyqtSignal(str)
    
        def __init__(self, function, *args, **kwargs):
            super(GenericWorker, self).__init__()
    
            self.function = function
            self.args = args
            self.kwargs = kwargs
            self.start.connect(self.run)
    
        @pyqtSlot()
        def run(self, *args, **kwargs):
            self.function(*self.args, **self.kwargs)
    
    
    # run
    app = QtGui.QApplication(sys.argv)
    test = MyApp()
    test.show()
    app.exec_()
    
    

    SO上答主的建议,在Worker里加一个finished signal

        def test(self):
            my_thread = QtCore.QThread()
            my_thread.start()
    
            # This causes my_worker.run() to eventually execute in my_thread:
            my_worker = GenericWorker(self.addBatch)
            my_worker.moveToThread(my_thread)
            my_worker.start.emit("hello")
            # 线程可以发signal到main window
            my_worker.finished.connect(self.xxx)
    
            self.threadPool.append(my_thread)
            self.my_worker = my_worker
    
    
    class GenericWorker(QtCore.QObject):
    
        start = pyqtSignal(str)
        finished=pyqtSignal()
        def __init__(self, function, *args, **kwargs):
            super(GenericWorker, self).__init__()
    
            self.function = function
            self.args = args
            self.kwargs = kwargs
            self.start.connect(self.run)
    
        @pyqtSlot()
        def run(self, *args, **kwargs):
            self.function(*self.args, **self.kwargs)
            self.finished.emit()
    
    

最后,验证一下是不是真的在单独的thread里了,用

def logthread(caller):
    # print('%-25s: %s, %s,' % (caller, QtCore.QThread.currentThread(), int(QtCore.QThread.currentThreadId())))
    print('%-25s: %s, %s,' % (caller, threading.current_thread().name,
                              threading.current_thread().ident))

效果:

mainwin.__init__         : MainThread, 140221684574016,
GenericWorker.__init__   : MainThread, 140221684574016,
GenericWorker.run        : Dummy-1, 140221265458944,
mainwin.addBatch         : Dummy-1, 140221265458944,
mainwin.add              : Dummy-1, 140221265458944,
mainwin.add              : Dummy-1, 140221265458944,
mainwin.add              : Dummy-1, 140221265458944,
mainwin.add              : Dummy-1, 140221265458944,
mainwin.add              : Dummy-1, 140221265458944,
mainwin.add              : Dummy-1, 140221265458944,
[Finished in 12.6s]

完整代码

import sys
import time
from PyQt4 import QtCore, QtGui
from PyQt4.QtCore import pyqtSignal, pyqtSlot
import threading


def logthread(caller):
    # print('%-25s: %s, %s,' % (caller, QtCore.QThread.currentThread(), int(QtCore.QThread.currentThreadId())))
    print('%-25s: %s, %s,' % (caller, threading.current_thread().name,
                              threading.current_thread().ident))


class MyApp(QtGui.QWidget):

    def __init__(self, parent=None):
        QtGui.QWidget.__init__(self, parent)

        self.setGeometry(300, 300, 280, 600)
        self.setWindowTitle('using threads')

        self.layout = QtGui.QVBoxLayout(self)

        self.testButton = QtGui.QPushButton("QThread")
        self.testButton.released.connect(self.test)
        self.listwidget = QtGui.QListWidget(self)

        self.layout.addWidget(self.testButton)
        self.layout.addWidget(self.listwidget)

        self.threadPool = []
        logthread('mainwin.__init__')

    def add(self, text):
        """ Add item to list widget """
        logthread('mainwin.add')
        # print "Add: " + text
        self.listwidget.addItem(text)
        self.listwidget.sortItems()

    def addBatch(self, text="test", iters=6, delay=0.3):
        """ Add several items to list widget """
        logthread('mainwin.addBatch')
        for i in range(iters):
            time.sleep(delay)  # artificial time delay
            self.add(text+" "+str(i))

    def test(self):
        my_thread = QtCore.QThread()
        my_thread.start()

        # This causes my_worker.run() to eventually execute in my_thread:
        my_worker = GenericWorker(self.addBatch)
        my_worker.moveToThread(my_thread)
        my_worker.start.emit("hello")
        # my_worker.finished.connect(self.xxx)

        self.threadPool.append(my_thread)
        self.my_worker = my_worker


class GenericWorker(QtCore.QObject):

    start = pyqtSignal(str)
    finished = pyqtSignal()

    def __init__(self, function, *args, **kwargs):
        super(GenericWorker, self).__init__()
        logthread('GenericWorker.__init__')
        self.function = function
        self.args = args
        self.kwargs = kwargs
        self.start.connect(self.run)

    @pyqtSlot()
    def run(self, *args, **kwargs):
        logthread('GenericWorker.run')
        self.function(*self.args, **self.kwargs)
        self.finished.emit()


# run
app = QtGui.QApplication(sys.argv)
test = MyApp()
test.show()
app.exec_()

方法二是lz自己根据几个答案理解拼凑的,网上没搜到完整例子,但是感觉不太对,请大神告知小弟如何修改

Rules of thumbs

When to subclass and when not to?

  • If you do not really need an event loop in the thread, you should subclass.
  • If you need an event loop and handle signals and slots within the thread, you may not need to subclass.

参考:
Threading with PyQt4 – Jo Plaete
How To Really, Truly Use QThreads; The Full Explanation
Threading without the headache
QThread Best Practices — When a QThread isn’t a thread part 2
You were not doing so wrong.
Background thread with QThread in PyQt
How to use QThread correctly in pyqt with moveToThread()?
Proper use of QThread.currentThreadId()
Python中使用线程的技巧
在PyQt里面如何做长操作时同时更新GUI

When a QThread isn’t a thread…
QThread Best Practices — When a QThread isn’t a thread part 2
Threads and QObjects | Qt 4.8 – Qt Documentation
Python线程同步机制: Locks, RLocks, Semaphores, Conditions, Events和Queues
Generic Worker Thread – PyQT

Advertisements

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s

%d 博主赞过: