Фонова нитка з QThread у PyQt


77

У мене є програма, яка взаємодіє з радіо, яке я використовую, через графічний інтерфейс, який я написав у PyQt. Очевидно, однією з основних функцій радіо є передача даних, але щоб робити це безперервно, мені доводиться циклічно записувати, що призводить до зависання графічного інтерфейсу. Оскільки я ніколи не мав справи з різьбленням, я намагався позбутися цих зависань, використовуючи QCoreApplication.processEvents().радіо. Однак радіо має спати між передачами, тому графічний інтерфейс все ще висить залежно від того, скільки триває цей режим сну.

Чи існує простий спосіб виправити це за допомогою QThread? Я шукав підручники про те, як реалізувати багатопоточність за допомогою PyQt, але більшість із них займаються налаштуванням серверів і набагато просунутіші, ніж мені потрібно. Мені, чесно кажучи, навіть не дуже потрібен мій потік, щоб щось оновлювати, поки він працює, мені просто потрібно його запустити, передати у фоновому режимі та зупинити.

Відповіді:


147

Я створив невеликий приклад, який показує 3 різні та прості способи роботи з нитками. Сподіваюся, це допоможе вам знайти правильний підхід до вашої проблеми.

import sys
import time

from PyQt5.QtCore import (QCoreApplication, QObject, QRunnable, QThread,
                          QThreadPool, pyqtSignal)


# Subclassing QThread
# http://qt-project.org/doc/latest/qthread.html
class AThread(QThread):

    def run(self):
        count = 0
        while count < 5:
            time.sleep(1)
            print("A Increasing")
            count += 1

# Subclassing QObject and using moveToThread
# http://blog.qt.digia.com/blog/2007/07/05/qthreads-no-longer-abstract
class SomeObject(QObject):

    finished = pyqtSignal()

    def long_running(self):
        count = 0
        while count < 5:
            time.sleep(1)
            print("B Increasing")
            count += 1
        self.finished.emit()

# Using a QRunnable
# http://qt-project.org/doc/latest/qthreadpool.html
# Note that a QRunnable isn't a subclass of QObject and therefore does
# not provide signals and slots.
class Runnable(QRunnable):

    def run(self):
        count = 0
        app = QCoreApplication.instance()
        while count < 5:
            print("C Increasing")
            time.sleep(1)
            count += 1
        app.quit()


def using_q_thread():
    app = QCoreApplication([])
    thread = AThread()
    thread.finished.connect(app.exit)
    thread.start()
    sys.exit(app.exec_())

def using_move_to_thread():
    app = QCoreApplication([])
    objThread = QThread()
    obj = SomeObject()
    obj.moveToThread(objThread)
    obj.finished.connect(objThread.quit)
    objThread.started.connect(obj.long_running)
    objThread.finished.connect(app.exit)
    objThread.start()
    sys.exit(app.exec_())

def using_q_runnable():
    app = QCoreApplication([])
    runnable = Runnable()
    QThreadPool.globalInstance().start(runnable)
    sys.exit(app.exec_())

if __name__ == "__main__":
    #using_q_thread()
    #using_move_to_thread()
    using_q_runnable()

Дякую, це, безумовно, виглядає корисно. Якщо я роблю це за допомогою методу QThread або QObject, чи можу я додати більше власних сигналів так само, як ви подали self.finishedсигнал? Наприклад, якщо замість того, щоб просто надрукувати рахунок, я хочу відобразити значення count у QSpinBox, яке є частиною мого графічного інтерфейсу з іншого класу.
gwenger

2
Так, ви можете додати власні сигнали. Одним із способів зробити це може бути випромінювання сигналу (наприклад, pyqtSignal (int)) із оновленим значенням і підключення до нього із вашого класу графічного інтерфейсу для оновлення QSpinBox відповідно.
aukaost

Я не впевнений , що рішення usingMoveToThread працює в моєму випадку, я спробував Розкоментувати usingMoveToThread()і коментування usingQThread, але коли я запускаю сценарій Збільшення ніколи не роздруковуються на термінал.
ilpoldo

1
Я знайшов дивний спосіб виправити це за допомогою PyQt 4.6. Здається, QThread :: run () неправильно викликається (я думаю, це має щось спільне з QThread :: run (), яка більше не є чистою віртуальною функцією). Це буде здаватися безглуздо, але щоб виправити це, просто створіть свій власний підклас QThread, повторно впровадіть run () та заповніть QThread.run (self). Це все, і це магічно працює
Метью Левін

2
Який із них ви пропонуєте для загального користування?
cxs1031,

52

Візьміть цю відповідь оновленою для PyQt5, python 3.4

Використовуйте це як шаблон для запуску працівника, який не бере дані та не повертає дані, оскільки вони доступні для форми.

1 - Клас Worker зменшено та додано до власного файлу worker.py для зручного запам'ятовування та незалежного повторного використання програмного забезпечення.

2 - Файл main.py - це файл, який визначає клас форми GUI

3 - Об’єкт потоку не є підкласом.

4 - І потоковий об'єкт, і робочий об'єкт належать до об'єкта Form

5 - Етапи процедури знаходяться в коментарях.

# worker.py
from PyQt5.QtCore import QThread, QObject, pyqtSignal, pyqtSlot
import time


class Worker(QObject):
    finished = pyqtSignal()
    intReady = pyqtSignal(int)


    @pyqtSlot()
    def procCounter(self): # A slot takes no params
        for i in range(1, 100):
            time.sleep(1)
            self.intReady.emit(i)

        self.finished.emit()

І основний файл:

  # main.py
  from PyQt5.QtCore import QThread
  from PyQt5.QtWidgets import QApplication, QLabel, QWidget, QGridLayout
  import sys
  import worker


  class Form(QWidget):

    def __init__(self):
       super().__init__()
       self.label = QLabel("0")

       # 1 - create Worker and Thread inside the Form
       self.obj = worker.Worker()  # no parent!
       self.thread = QThread()  # no parent!

       # 2 - Connect Worker`s Signals to Form method slots to post data.
       self.obj.intReady.connect(self.onIntReady)

       # 3 - Move the Worker object to the Thread object
       self.obj.moveToThread(self.thread)

       # 4 - Connect Worker Signals to the Thread slots
       self.obj.finished.connect(self.thread.quit)

       # 5 - Connect Thread started signal to Worker operational slot method
       self.thread.started.connect(self.obj.procCounter)

       # * - Thread finished signal will close the app if you want!
       #self.thread.finished.connect(app.exit)

       # 6 - Start the thread
       self.thread.start()

       # 7 - Start the form
       self.initUI()


    def initUI(self):
        grid = QGridLayout()
        self.setLayout(grid)
        grid.addWidget(self.label,0,0)

        self.move(300, 150)
        self.setWindowTitle('thread test')
        self.show()

    def onIntReady(self, i):
        self.label.setText("{}".format(i))
        #print(i)

    app = QApplication(sys.argv)

    form = Form()

    sys.exit(app.exec_())

3
Для тих, хто не обізнаний, див. Це запитання, чому використання декоратора pyqtSlot () `та конкретний порядок підключення сигналу, зазначений у цій відповіді, має значення.
three_pineapples

1
Ви знахідка, дякую, дякую, дякую, що підкреслили той факт, що Робітник і Нитка не мали мати батьків! Я мав справу з QThread: Destroyed while thread is still runningостанніми трьома годинами, потім прочитав це, і воно клацнуло!
Skybbles

чувак ... буквальний бог
real_hagrid

36

Дуже хороший приклад від Метта, я виправив помилку, а також pyqt4.8 є звичним зараз, тому я також видалив фіктивний клас і додав приклад для сигналу dataReady

# -*- coding: utf-8 -*-
import sys
from PyQt4 import QtCore, QtGui
from PyQt4.QtCore import Qt


# very testable class (hint: you can use mock.Mock for the signals)
class Worker(QtCore.QObject):
    finished = QtCore.pyqtSignal()
    dataReady = QtCore.pyqtSignal(list, dict)

    @QtCore.pyqtSlot()
    def processA(self):
        print "Worker.processA()"
        self.finished.emit()

    @QtCore.pyqtSlot(str, list, list)
    def processB(self, foo, bar=None, baz=None):
        print "Worker.processB()"
        for thing in bar:
            # lots of processing...
            self.dataReady.emit(['dummy', 'data'], {'dummy': ['data']})
        self.finished.emit()


def onDataReady(aList, aDict):
    print 'onDataReady'
    print repr(aList)
    print repr(aDict)


app = QtGui.QApplication(sys.argv)

thread = QtCore.QThread()  # no parent!
obj = Worker()  # no parent!
obj.dataReady.connect(onDataReady)

obj.moveToThread(thread)

# if you want the thread to stop after the worker is done
# you can always call thread.start() again later
obj.finished.connect(thread.quit)

# one way to do it is to start processing as soon as the thread starts
# this is okay in some cases... but makes it harder to send data to
# the worker object from the main gui thread.  As you can see I'm calling
# processA() which takes no arguments
thread.started.connect(obj.processA)
thread.finished.connect(app.exit)

thread.start()

# another way to do it, which is a bit fancier, allows you to talk back and
# forth with the object in a thread safe way by communicating through signals
# and slots (now that the thread is running I can start calling methods on
# the worker object)
QtCore.QMetaObject.invokeMethod(obj, 'processB', Qt.QueuedConnection,
                                QtCore.Q_ARG(str, "Hello World!"),
                                QtCore.Q_ARG(list, ["args", 0, 1]),
                                QtCore.Q_ARG(list, []))

# that looks a bit scary, but its a totally ok thing to do in Qt,
# we're simply using the system that Signals and Slots are built on top of,
# the QMetaObject, to make it act like we safely emitted a signal for
# the worker thread to pick up when its event loop resumes (so if its doing
# a bunch of work you can call this method 10 times and it will just queue
# up the calls.  Note: PyQt > 4.6 will not allow you to pass in a None
# instead of an empty list, it has stricter type checking

app.exec_()

1
Дуже мило. Це має бути прийнятою відповіддю сьогодні!
Триларіон

9
Це QMetaObjectнегарно, як пекло
astrojuanlu

Так, QMetaObject потворний як гріх, але під капотом саме це використовують сигнали та слоти Qt. Це дозволяє абоненту ефективно додавати повідомлення до черги сигналів Worker безпечним способом. Ознайомтесь із моєю відповіддю щодо використання QRunnables. У нього також можуть бути свої плями, але він дуже потужний у отриманні асинхронної поведінки у вашому додатку PyQt. Він також не використовує QMetaObject
Метью Левін

Очевидно, успадкування від qthread прекрасне: woboq.com/blog/qthread-you-were-not-doing-so-wrong.html
Ніл Г,

2
Ні, це не повинна бути прийнятою відповіддю. Немає абсолютно жодної вагомої причини використовувати QMetaObjectхаки, засновані на цілій стіні коментарів, коли ви можете просто визначити правильні підключення до слота сигналу. </sigh>
Сесіл Каррі,

32

На думку розробників Qt, підкласифікація QThread є неправильною (див. Http://blog.qt.io/blog/2010/06/17/youre-doing-it-wrong/ ). Але цю статтю насправді важко зрозуміти (плюс заголовок трохи поблажливий). Я знайшов кращу публікацію в блозі, яка дає більш детальне пояснення того, чому слід використовувати один стиль обробки ниток над іншим: http://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use- qthreads-повне пояснення /

На мій погляд, вам, мабуть, ніколи не слід підкласувати потік з метою перевантаження методу запуску. Хоча це працює, ви в основному обходите те, як Qt хоче, щоб ви працювали. До того ж ви пропустите такі речі, як події та належні безпечні сигнали та слоти. Плюс, як ви, мабуть, побачите у наведеному вище дописі в блозі, "правильний" спосіб створення потоків змушує вас писати більш перевіряваний код.

Ось кілька прикладів того, як скористатися перевагами QThreads у PyQt (нижче я опублікував окрему відповідь, яка належним чином використовує QRunnable та включає сигнали / слоти, така відповідь буде кращою, якщо у вас багато асинхронних завдань, які вам потрібні для балансу навантаження) .

import sys
from PyQt4 import QtCore
from PyQt4 import QtGui
from PyQt4.QtCore import Qt

# very testable class (hint: you can use mock.Mock for the signals)
class Worker(QtCore.QObject):
    finished = QtCore.pyqtSignal()
    dataReady = QtCore.pyqtSignal(list, dict)

    @QtCore.pyqtSlot()
    def processA(self):
        print "Worker.processA()"
        self.finished.emit()

    @QtCore.pyqtSlot(str, list, list)
    def processB(self, foo, bar=None, baz=None):
        print "Worker.processB()"
        for thing in bar:
            # lots of processing...
            self.dataReady.emit(['dummy', 'data'], {'dummy': ['data']})
        self.finished.emit()


class Thread(QtCore.QThread):
    """Need for PyQt4 <= 4.6 only"""
    def __init__(self, parent=None):
        QtCore.QThread.__init__(self, parent)

     # this class is solely needed for these two methods, there
     # appears to be a bug in PyQt 4.6 that requires you to
     # explicitly call run and start from the subclass in order
     # to get the thread to actually start an event loop

    def start(self):
        QtCore.QThread.start(self)

    def run(self):
        QtCore.QThread.run(self)


app = QtGui.QApplication(sys.argv)

thread = Thread() # no parent!
obj = Worker() # no parent!
obj.moveToThread(thread)

# if you want the thread to stop after the worker is done
# you can always call thread.start() again later
obj.finished.connect(thread.quit)

# one way to do it is to start processing as soon as the thread starts
# this is okay in some cases... but makes it harder to send data to
# the worker object from the main gui thread.  As you can see I'm calling
# processA() which takes no arguments
thread.started.connect(obj.processA)
thread.start()

# another way to do it, which is a bit fancier, allows you to talk back and
# forth with the object in a thread safe way by communicating through signals
# and slots (now that the thread is running I can start calling methods on
# the worker object)
QtCore.QMetaObject.invokeMethod(obj, 'processB', Qt.QueuedConnection,
                                QtCore.Q_ARG(str, "Hello World!"),
                                QtCore.Q_ARG(list, ["args", 0, 1]),
                                QtCore.Q_ARG(list, []))

# that looks a bit scary, but its a totally ok thing to do in Qt,
# we're simply using the system that Signals and Slots are built on top of,
# the QMetaObject, to make it act like we safely emitted a signal for 
# the worker thread to pick up when its event loop resumes (so if its doing
# a bunch of work you can call this method 10 times and it will just queue
# up the calls.  Note: PyQt > 4.6 will not allow you to pass in a None
# instead of an empty list, it has stricter type checking

app.exec_()

# Without this you may get weird QThread messages in the shell on exit
app.deleteLater()        

2
З іншого Qt Dev woboq.com/blog/qthread-you-were-not-doing-so-wrong.html підкласифікація QThread є цілком прийнятною, коли просто впроваджується метод запуску.
simotek 04.03.15

appears to be a bug in PyQt 4.6Ви можете вказати на проблему, щоб ми знали, чи / коли вона виправлена.
dashesy

Проголосуючи за посилання на статтю Пош, оскільки воно прояснює суперечливі погляди.
Michael Scheper

1
Очевидно, успадкування від qthread прекрасне: woboq.com/blog/qthread-you-were-not-doing-so-wrong.html
Ніл Г,

1
Ваше посилання застаріле, можливо, ви захочете оновити його: blog.qt.io/blog/2010/06/17/youre-doing-it-wrong
Черно,

9

У PyQt існує безліч варіантів отримання асинхронної поведінки. Для речей, які потребують обробки подій (наприклад, QtNetwork тощо), слід використовувати приклад QThread, який я навів у своїй іншій відповіді на цю тему. Але для переважної більшості ваших потреб у потоках я думаю, що це рішення набагато перевершує інші методи.

Перевага цього полягає в тому, що QThreadPool планує ваші екземпляри QRunnable як завдання. Це схоже на шаблон завдання, який використовується в TBB від Intel. Це не настільки елегантно, як мені подобається, але воно дає чудову асинхронну поведінку.

Це дозволяє використовувати більшу частину потокової потужності Qt в Python за допомогою QRunnable і при цьому користуватися перевагами сигналів і слотів. Я використовую цей самий код у декількох програмах, деякі з яких роблять сотні асинхронних викликів REST, деякі відкривають файли або каталоги зі списком, і найкраща частина - використання цього методу, завдання Qt збалансовує системні ресурси для мене.

import time
from PyQt4 import QtCore
from PyQt4 import QtGui
from PyQt4.QtCore import Qt


def async(method, args, uid, readycb, errorcb=None):
    """
    Asynchronously runs a task

    :param func method: the method to run in a thread
    :param object uid: a unique identifier for this task (used for verification)
    :param slot updatecb: the callback when data is receieved cb(uid, data)
    :param slot errorcb: the callback when there is an error cb(uid, errmsg)

    The uid option is useful when the calling code makes multiple async calls
    and the callbacks need some context about what was sent to the async method.
    For example, if you use this method to thread a long running database call
    and the user decides they want to cancel it and start a different one, the
    first one may complete before you have a chance to cancel the task.  In that
    case, the "readycb" will be called with the cancelled task's data.  The uid
    can be used to differentiate those two calls (ie. using the sql query).

    :returns: Request instance
    """
    request = Request(method, args, uid, readycb, errorcb)
    QtCore.QThreadPool.globalInstance().start(request)
    return request


class Request(QtCore.QRunnable):
    """
    A Qt object that represents an asynchronous task

    :param func method: the method to call
    :param list args: list of arguments to pass to method
    :param object uid: a unique identifier (used for verification)
    :param slot readycb: the callback used when data is receieved
    :param slot errorcb: the callback used when there is an error

    The uid param is sent to your error and update callbacks as the
    first argument. It's there to verify the data you're returning

    After created it should be used by invoking:

    .. code-block:: python

       task = Request(...)
       QtCore.QThreadPool.globalInstance().start(task)

    """
    INSTANCES = []
    FINISHED = []
    def __init__(self, method, args, uid, readycb, errorcb=None):
        super(Request, self).__init__()
        self.setAutoDelete(True)
        self.cancelled = False

        self.method = method
        self.args = args
        self.uid = uid
        self.dataReady = readycb
        self.dataError = errorcb

        Request.INSTANCES.append(self)

        # release all of the finished tasks
        Request.FINISHED = []

    def run(self):
        """
        Method automatically called by Qt when the runnable is ready to run.
        This will run in a separate thread.
        """
        # this allows us to "cancel" queued tasks if needed, should be done
        # on shutdown to prevent the app from hanging
        if self.cancelled:
            self.cleanup()
            return

        # runs in a separate thread, for proper async signal/slot behavior
        # the object that emits the signals must be created in this thread.
        # Its not possible to run grabber.moveToThread(QThread.currentThread())
        # so to get this QObject to properly exhibit asynchronous
        # signal and slot behavior it needs to live in the thread that
        # we're running in, creating the object from within this thread
        # is an easy way to do that.
        grabber = Requester()
        grabber.Loaded.connect(self.dataReady, Qt.QueuedConnection)
        if self.dataError is not None:
            grabber.Error.connect(self.dataError, Qt.QueuedConnection)

        try:
            result = self.method(*self.args)
            if self.cancelled:
                # cleanup happens in 'finally' statement
                return
            grabber.Loaded.emit(self.uid, result)
        except Exception as error:
            if self.cancelled:
                # cleanup happens in 'finally' statement
                return
            grabber.Error.emit(self.uid, unicode(error))
        finally:
            # this will run even if one of the above return statements
            # is executed inside of the try/except statement see:
            # https://docs.python.org/2.7/tutorial/errors.html#defining-clean-up-actions
            self.cleanup(grabber)

    def cleanup(self, grabber=None):
        # remove references to any object or method for proper ref counting
        self.method = None
        self.args = None
        self.uid = None
        self.dataReady = None
        self.dataError = None

        if grabber is not None:
            grabber.deleteLater()

        # make sure this python obj gets cleaned up
        self.remove()

    def remove(self):
        try:
            Request.INSTANCES.remove(self)

            # when the next request is created, it will clean this one up
            # this will help us avoid this object being cleaned up
            # when it's still being used
            Request.FINISHED.append(self)
        except ValueError:
            # there might be a race condition on shutdown, when shutdown()
            # is called while the thread is still running and the instance
            # has already been removed from the list
            return

    @staticmethod
    def shutdown():
        for inst in Request.INSTANCES:
            inst.cancelled = True
        Request.INSTANCES = []
        Request.FINISHED = []


class Requester(QtCore.QObject):
    """
    A simple object designed to be used in a separate thread to allow
    for asynchronous data fetching
    """

    #
    # Signals
    #

    Error = QtCore.pyqtSignal(object, unicode)
    """
    Emitted if the fetch fails for any reason

    :param unicode uid: an id to identify this request
    :param unicode error: the error message
    """

    Loaded = QtCore.pyqtSignal(object, object)
    """
    Emitted whenever data comes back successfully

    :param unicode uid: an id to identify this request
    :param list data: the json list returned from the GET
    """

    NetworkConnectionError = QtCore.pyqtSignal(unicode)
    """
    Emitted when the task fails due to a network connection error

    :param unicode message: network connection error message
    """

    def __init__(self, parent=None):
        super(Requester, self).__init__(parent)


class ExampleObject(QtCore.QObject):
    def __init__(self, parent=None):
        super(ExampleObject, self).__init__(parent)
        self.uid = 0
        self.request = None

    def ready_callback(self, uid, result):
        if uid != self.uid:
            return
        print "Data ready from %s: %s" % (uid, result)

    def error_callback(self, uid, error):
        if uid != self.uid:
            return
        print "Data error from %s: %s" % (uid, error)

    def fetch(self):
        if self.request is not None:
            # cancel any pending requests
            self.request.cancelled = True
            self.request = None

        self.uid += 1
        self.request = async(slow_method, ["arg1", "arg2"], self.uid,
                             self.ready_callback,
                             self.error_callback)


def slow_method(arg1, arg2):
    print "Starting slow method"
    time.sleep(1)
    return arg1 + arg2


if __name__ == "__main__":
    import sys
    app = QtGui.QApplication(sys.argv)

    obj = ExampleObject()

    dialog = QtGui.QDialog()
    layout = QtGui.QVBoxLayout(dialog)
    button = QtGui.QPushButton("Generate", dialog)
    progress = QtGui.QProgressBar(dialog)
    progress.setRange(0, 0)
    layout.addWidget(button)
    layout.addWidget(progress)
    button.clicked.connect(obj.fetch)
    dialog.show()

    app.exec_()
    app.deleteLater() # avoids some QThread messages in the shell on exit
    # cancel all running tasks avoid QThread/QTimer error messages
    # on exit
    Request.shutdown()

Виходячи з програми, ви хочете переконатися, що скасували всі завдання, або програма зависне, доки не завершиться кожне заплановане завдання


1
Це відмінна відповідь - ДЯКУЮ! Існує публікація в блозі Багатопотокові програми PyQt із QThreadPool, яка має подібний підхід.
Філ

5

На основі методів об’єктів Worker, згаданих в інших відповідях, я вирішив подивитися, чи можу я розширити рішення, щоб викликати більше потоків - в цьому випадку оптимальне число, яке може запустити машина, і закрутити декількох працівників з невизначеним часом завершення. Для цього мені все одно потрібно підклас QThread - але лише для присвоєння номера потоку та для «перевстановлення» сигналів «закінчений» та «запущений» для включення номера потоку.

Я досить зосередився на сигналах між головним графічним інтерфейсом, потоками та працівниками.

Подібним чином, інші відповіді були великим зусиллям, щоб вказати на те, що не є вихователем QThread, але я не думаю, що це справді хвилює. Однак мій код також обережно знищує об'єкти QThread.

Однак я не зміг виховувати робочі об'єкти, тому бажано надіслати їм сигнал deleteLater (), або коли функція потоку закінчена, або графічний інтерфейс знищений. У мене був свій власний код, який не робив цього.

Ще одним удосконаленням, яке, на мою думку, було необхідним, було перевтілення closeEvent графічного інтерфейсу (QWidget) таким чином, щоб потокам було наказано вийти, а потім графічний інтерфейс зачекав, поки всі потоки не закінчаться. Коли я грав з деякими іншими відповідями на це питання, я отримав QThread знищені помилки.

Можливо, це буде корисно іншим. Я, звичайно, знайшов це корисною вправою. Можливо, інші знатимуть кращий спосіб для нитки оголосити свою ідентичність.

#!/usr/bin/env python3
#coding:utf-8
# Author:   --<>
# Purpose:  To demonstrate creation of multiple threads and identify the receipt of thread results
# Created: 19/12/15

import sys


from PyQt4.QtCore import QThread, pyqtSlot, pyqtSignal
from PyQt4.QtGui import QApplication, QLabel, QWidget, QGridLayout

import sys
import worker

class Thread(QThread):
    #make new signals to be able to return an id for the thread
    startedx = pyqtSignal(int)
    finishedx = pyqtSignal(int)

    def __init__(self,i,parent=None):
        super().__init__(parent)
        self.idd = i

        self.started.connect(self.starttt)
        self.finished.connect(self.finisheddd)

    @pyqtSlot()
    def starttt(self):
        print('started signal from thread emitted')
        self.startedx.emit(self.idd) 

    @pyqtSlot()
    def finisheddd(self):
        print('finished signal from thread emitted')
        self.finishedx.emit(self.idd)

class Form(QWidget):

    def __init__(self):
        super().__init__()

        self.initUI()

        self.worker={}
        self.threadx={}
        self.i=0
        i=0

        #Establish the maximum number of threads the machine can optimally handle
        #Generally relates to the number of processors

        self.threadtest = QThread(self)
        self.idealthreadcount = self.threadtest.idealThreadCount()

        print("This machine can handle {} threads optimally".format(self.idealthreadcount))

        while i <self.idealthreadcount:
            self.setupThread(i)
            i+=1

        i=0
        while i<self.idealthreadcount:
            self.startThread(i)
            i+=1

        print("Main Gui running in thread {}.".format(self.thread()))


    def setupThread(self,i):

        self.worker[i]= worker.Worker(i)  # no parent!
        #print("Worker object runningt in thread {} prior to movetothread".format(self.worker[i].thread()) )
        self.threadx[i] = Thread(i,parent=self)  #  if parent isn't specified then need to be careful to destroy thread 
        self.threadx[i].setObjectName("python thread{}"+str(i))
        #print("Thread object runningt in thread {} prior to movetothread".format(self.threadx[i].thread()) )
        self.threadx[i].startedx.connect(self.threadStarted)
        self.threadx[i].finishedx.connect(self.threadFinished)

        self.worker[i].finished.connect(self.workerFinished)
        self.worker[i].intReady.connect(self.workerResultReady)

        #The next line is optional, you may want to start the threads again without having to create all the code again.
        self.worker[i].finished.connect(self.threadx[i].quit)

        self.threadx[i].started.connect(self.worker[i].procCounter)

        self.destroyed.connect(self.threadx[i].deleteLater)
        self.destroyed.connect(self.worker[i].deleteLater)

        #This is the key code that actually get the worker code onto another processor or thread.
        self.worker[i].moveToThread(self.threadx[i])

    def startThread(self,i):
        self.threadx[i].start()

    @pyqtSlot(int)
    def threadStarted(self,i):
        print('Thread {}  started'.format(i))
        print("Thread priority is {}".format(self.threadx[i].priority()))        


    @pyqtSlot(int)
    def threadFinished(self,i):
        print('Thread {} finished'.format(i))




    @pyqtSlot(int)
    def threadTerminated(self,i):
        print("Thread {} terminated".format(i))

    @pyqtSlot(int,int)
    def workerResultReady(self,j,i):
        print('Worker {} result returned'.format(i))
        if i ==0:
            self.label1.setText("{}".format(j))
        if i ==1:
            self.label2.setText("{}".format(j))
        if i ==2:
            self.label3.setText("{}".format(j))
        if i ==3:
            self.label4.setText("{}".format(j)) 

        #print('Thread {} has started'.format(self.threadx[i].currentThreadId()))    

    @pyqtSlot(int)
    def workerFinished(self,i):
        print('Worker {} finished'.format(i))

    def initUI(self):
        self.label1 = QLabel("0")
        self.label2= QLabel("0")
        self.label3= QLabel("0")
        self.label4 = QLabel("0")
        grid = QGridLayout(self)
        self.setLayout(grid)
        grid.addWidget(self.label1,0,0)
        grid.addWidget(self.label2,0,1) 
        grid.addWidget(self.label3,0,2) 
        grid.addWidget(self.label4,0,3) #Layout parents the self.labels

        self.move(300, 150)
        self.setGeometry(0,0,300,300)
        #self.size(300,300)
        self.setWindowTitle('thread test')
        self.show()

    def closeEvent(self, event):
        print('Closing')

        #this tells the threads to stop running
        i=0
        while i <self.idealthreadcount:
            self.threadx[i].quit()
            i+=1

         #this ensures window cannot be closed until the threads have finished.
        i=0
        while i <self.idealthreadcount:
            self.threadx[i].wait() 
            i+=1        


        event.accept()


if __name__=='__main__':
    app = QApplication(sys.argv)
    form = Form()
    sys.exit(app.exec_())

І робочий код нижче

#!/usr/bin/env python3
#coding:utf-8
# Author:   --<>
# Purpose:  Stack Overflow
# Created: 19/12/15

import sys
import unittest


from PyQt4.QtCore import QThread, QObject, pyqtSignal, pyqtSlot
import time
import random


class Worker(QObject):
    finished = pyqtSignal(int)
    intReady = pyqtSignal(int,int)

    def __init__(self, i=0):
        '''__init__ is called while the worker is still in the Gui thread. Do not put slow or CPU intensive code in the __init__ method'''

        super().__init__()
        self.idd = i



    @pyqtSlot()
    def procCounter(self): # This slot takes no params
        for j in range(1, 10):
            random_time = random.weibullvariate(1,2)
            time.sleep(random_time)
            self.intReady.emit(j,self.idd)
            print('Worker {0} in thread {1}'.format(self.idd, self.thread().idd))

        self.finished.emit(self.idd)


if __name__=='__main__':
    unittest.main()
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.