Главная страница

ээдд. Прохоренок_Н_А__Дронов_В_А_Python_3_и_PyQt_5_Разработка_приложен. Николай Прохоренок Владимир Дронов


Скачать 7.92 Mb.
НазваниеНиколай Прохоренок Владимир Дронов
Дата05.05.2023
Размер7.92 Mb.
Формат файлаpdf
Имя файлаПрохоренок_Н_А__Дронов_В_А_Python_3_и_PyQt_5_Разработка_приложен.pdf
ТипДокументы
#1111379
страница33 из 83
1   ...   29   30   31   32   33   34   35   36   ...   83
QtWidgets.QWidget.__init__(self, parent) self.label = QtWidgets.QLabel("Нажмите кнопку для запуска потока") self.label.setAlignment(QtCore.Qt.AlignHCenter) self.button = QtWidgets.QPushButton("Запустить процесс") self.vbox = QtWidgets.QVBoxLayout() self.vbox.addWidget(self.label) self.vbox.addWidget(self.button) self.setLayout(self.vbox) self.mythread = MyThread() # Создаем экземпляр класса self.button.clicked.connect(self.on_clicked) self.mythread.started.connect(self.on_started) self.mythread.finished.connect(self.on_finished) self.mythread.mysignal.connect(self.on_change, QtCore.Qt.QueuedConnection) def on_clicked(self): self.button.setDisabled(True) # Делаем кнопку неактивной self.mythread.start() # Запускаем поток def on_started(self): # Вызывается при запуске потока self.label.setText("Вызван метод on_started()")

346
Часть II. Библиотека PyQt 5 def on_finished(self): # Вызывается при завершении потока self.label.setText("Вызван метод on_finished()") self.button.setDisabled(False) # Делаем кнопку активной def on_change(self, s): self.label.setText(s) if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) window = MyWindow() window.setWindowTitle("Использование класса QThread") window.resize(300, 70) window.show() sys.exit(app.exec_())
Здесь мы создали класс
MyThread
, который является наследником класса
QThread
. В нем мы определили свой собственный сигнал mysignal
, для чего создали атрибут с таким же име- нем и занесли в него значение, возвращенное функцией pyqtSignal()
из модуля
QtCore
Функции pyqtSignal()
мы передали в качестве параметра тип str
(строка Python), тем са- мым указав PyQt, что вновь определенный сигнал будет принимать единственный параметр строкового типа: mysignal = QtCore.pyqtSignal(str)
В том же классе мы определили обязательный для потоков метод run()
— в нем произво- дится имитация процесса с помощью цикла for и метода sleep()
: каждые три секунды вы- полняется генерация сигнала mysignal и передача текущего значения переменной i
в соста- ве строки: self.mysignal.emit("i = %s" % i)
Внутри конструктора класса
MyWindow мы назначили обработчик этого сигнала с помощью выражения: self.mythread.mysignal.connect(self.on_change, QtCore.Qt.QueuedConnection)
Здесь все нам уже знакомо: у свойства mysignal потока, которое представляет одноименный сигнал, вызывается метод connect()
, и ему первым параметром передается обработчик. Во втором параметре метода connect()
с помощью атрибута
QueuedConnection указывается, что сигнал помещается в очередь обработки событий, и обработчик должен выполняться в по- токе приемника сигнала, т. е. в GUI-потоке. Из GUI-потока мы можем смело изменять свой- ства компонентов интерфейса.
Теперь рассмотрим код метода класса
MyWindow
, который станет обработчиком сигнала mysignal
: def on_change(self, s): self.label.setText(s)
Второй параметр этого метода служит для приема параметра, переданного этому сигналу.
Значение этого параметра будет выведено в надписи с помощью метода setText()
Еще в конструкторе класса
MyWindow производится создание надписи и кнопки, а затем их размещение внутри вертикального контейнера. Далее выполняется создание экземпляра класса
MyThread и сохранение его в атрибуте mythread
. С помощью этого атрибута мы мо-

Глава 17. Знакомство с PyQt 5 347 жем управлять потоком и назначить обработчики сигналов started()
, finished()
и mysignal
. Запуск потока производится с помощью метода start()
внутри обработчика на- жатия кнопки. Чтобы исключить повторный запуск потока, мы с помощью метода setDisabled() делаем кнопку неактивной, а после окончания работы потока внутри обра- ботчика сигнала finished()
опять делаем кнопку активной.
Обратите внимание, что для имитации длительного процесса мы использовали статический метод sleep()
из класса
QThread
, а не функцию sleep()
из модуля time
. Вообще, приоста- новить выполнение потока позволяют следующие статические методы класса
QThread
:
 sleep()
— продолжительность задается в секундах:
QtCore.QThread.sleep(3) # "Засыпаем" на 3 секунды
 msleep()
— продолжительность задается в миллисекундах:
QtCore.QThread.msleep(3000) # "Засыпаем" на 3 секунды
 usleep()
— продолжительность задается в микросекундах:
QtCore.QThread.usleep(3000000) # "Засыпаем" на 3 секунды
Еще один полезный статичный метод класса
QThread
— yieldCurrentThread()
— немедлен- но приостанавливает выполнение текущего потока и передает управление следующему ожидающему выполнения потоку, если таковой есть:
QtCore.QThread.yieldCurrentThread()
17.9.2. Управление циклом внутри потока
Очень часто внутри потока одни и те же инструкции выполняются многократно. Например, при осуществлении мониторинга серверов в Интернете на каждой итерации цикла посыла- ется запрос к одному и тому же серверу. При этом внутри метода run()
используется беско- нечный цикл, выход из которого производится после окончания опроса всех серверов.
В некоторых случаях этот цикл необходимо прервать преждевременно по нажатию кнопки пользователем. Чтобы это стало возможным, в классе, реализующем поток, следует создать атрибут, который будет содержать флаг текущего состояния. Далее на каждой итерации цикла проверяется состояние флага и при его изменении прерывается выполнение цикла.
Чтобы изменить значение атрибута, создаем обработчик и связываем его с сигналом clicked()
соответствующей кнопки. При нажатии кнопки внутри обработчика производим изменение значения атрибута. Пример запуска и остановки потока с помощью кнопок при- веден в листинге 17.14.
Листинг 17.14. Запуск и остановка потока
# -*- coding: utf-8 -*- from PyQt5 import QtCore, QtWidgets class MyThread(QtCore.QThread): mysignal = QtCore.pyqtSignal(str) def __init__(self, parent=None):
QtCore.QThread.__init__(self, parent) self.running = False # Флаг выполнения self.count = 0 def run(self): self.running = True

348
Часть II. Библиотека PyQt 5 while self.running: # Проверяем значение флага self.count += 1 self.mysignal.emit("count = %s" % self.count) self.sleep(1) # Имитируем процесс class MyWindow(QtWidgets.QWidget): def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self, parent) self.label = QtWidgets.QLabel("Нажмите кнопку для запуска потока") self.label.setAlignment(QtCore.Qt.AlignHCenter) self.btnStart = QtWidgets.QPushButton("Запустить поток") self.btnStop = QtWidgets.QPushButton("Остановить поток") self.vbox = QtWidgets.QVBoxLayout() self.vbox.addWidget(self.label) self.vbox.addWidget(self.btnStart) self.vbox.addWidget(self.btnStop) self.setLayout(self.vbox) self.mythread = MyThread() self.btnStart.clicked.connect(self.on_start) self.btnStop.clicked.connect(self.on_stop) self.mythread.mysignal.connect(self.on_change, QtCore.Qt.QueuedConnection) def on_start(self): if not self.mythread.isRunning(): self.mythread.start() # Запускаем поток def on_stop(self): self.mythread.running = False # Изменяем флаг выполнения def on_change(self, s): self.label.setText(s) def closeEvent(self, event): # Вызывается при закрытии окна self.hide() # Скрываем окно self.mythread.running = False # Изменяем флаг выполнения self.mythread.wait(5000) # Даем время, чтобы закончить event.accept() # Закрываем окно if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) window = MyWindow() window.setWindowTitle("Запуск и остановка потока") window.resize(300, 100) window.show() sys.exit(app.exec_())
В этом примере в конструкторе класса
MyThread создается атрибут running
, и ему присваи- вается значение
False
. При запуске потока внутри метода run()
значение атрибута изменя- ется на
True
. Затем запускается цикл, в котором атрибут указывается в качестве условия.
Как только значение атрибута станет равным значению
False
, цикл будет остановлен.
Внутри конструктора класса
MyWindow производится создание надписи, двух кнопок и эк- земпляра класса
MyThread
. Далее назначаются обработчики сигналов. При нажатии кнопки

Глава 17. Знакомство с PyQt 5 349
Запустить поток запустится метод on_start()
, внутри которого с помощью метода isRunning()
производится проверка текущего статуса потока. Если поток не запущен, выполняется его запуск вызовом метода start()
. При нажатии кнопки Остановить поток запустится метод on_stop()
, в котором атрибуту running присваивается значение
False
. Это значение является условием выхода из цикла внутри метода run()
Путем изменения значения атрибута можно прервать выполнение цикла только в том слу- чае, если закончилось выполнение очередной итерации. Если поток длительное время ожи- дает какого-либо события (например, ответа сервера), можно так и не дождаться заверше- ния потока. Чтобы принудительно прервать выполнение потока, следует воспользоваться методом terminate()
. Однако к этому методу рекомендуется прибегать только в крайнем случае, поскольку прерывание производится в любой части кода. При этом блокировки ав- томатически не снимаются, а кроме того, можно повредить данные, над которыми произво- дились операции в момент прерывания. После вызова метода terminate()
следует вызвать метод wait()
При закрытии окна приложение завершает работу, что также приводит к завершению всех потоков. Чтобы предотвратить повреждение данных, следует перехватить событие закрытия окна и дождаться окончания выполнения. Чтобы перехватить событие, необходимо внутри класса создать метод с предопределенным названием, в нашем случае — с названием closeEvent()
. Этот метод будет автоматически вызван при попытке закрыть окно. В качест- ве параметра метод принимает объект события event
, через который можно получить до- полнительную информацию о событии. Чтобы закрыть окно внутри метода closeEvent()
, следует вызвать метод accept()
объекта события. Если необходимо предотвратить закрытие окна, то следует вызвать метод ignore()
Внутри метода closeEvent()
мы присваиваем атрибуту running значение
False
. Далее с по- мощью метода wait()
даем возможность потоку нормально завершить работу. В качестве параметра метод wait()
принимает количество миллисекунд, по истечении которых управ- ление будет передано следующей инструкции. Необходимо учитывать, что это максималь- ное время: если поток закончит работу раньше, то и метод закончит выполнение раньше.
Метод wait()
возвращает значение
True
, если поток успешно завершил работу, и
False
— в противном случае. Ожидание завершения потока занимает некоторое время, в течение которого окно будет по-прежнему видимым. Чтобы не вводить пользователя в заблуждение, в самом начале метода closeEvent()
мы скрываем окно вызовом метода hide()
Каждый поток может иметь собственный цикл обработки сигналов, который запускается с помощью метода exec_()
. В этом случае потоки могут обмениваться сигналами между собой. Чтобы прервать цикл, следует вызвать слот quit()
или метод exit([returnCode=0])
Рассмотрим обмен сигналами между потоками на примере (листинг 17.15).
Листинг 17.15. Обмен сигналами между потоками
# -*- coding: utf-8 -*- from PyQt5 import QtCore, QtWidgets class Thread1(QtCore.QThread): s1 = QtCore.pyqtSignal(int) def __init__(self, parent=None):
QtCore.QThread.__init__(self, parent) self.count = 0

350
Часть II. Библиотека PyQt 5 def run(self): self.exec_() # Запускаем цикл обработки сигналов def on_start(self): self.count += 1 self.s1.emit(self.count) class Thread2(QtCore.QThread): s2 = QtCore.pyqtSignal(str) def __init__(self, parent=None):
QtCore.QThread.__init__(self, parent) def run(self): self.exec_() # Запускаем цикл обработки сигналов def on_change(self, i): i += 10 self.s2.emit("%d" % i) class MyWindow(QtWidgets.QWidget): def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self, parent) self.label = QtWidgets.QLabel("Нажмите кнопку") self.label.setAlignment(QtCore.Qt.AlignHCenter) self.button = QtWidgets.QPushButton("Сгенерировать сигнал") self.vbox = QtWidgets.QVBoxLayout() self.vbox.addWidget(self.label) self.vbox.addWidget(self.button) self.setLayout(self.vbox) self.thread1 = Thread1() self.thread2 = Thread2() self.thread1.start() self.thread2.start() self.button.clicked.connect(self.thread1.on_start) self.thread1.s1.connect(self.thread2.on_change) self.thread2.s2.connect(self.on_thread2_s2) def on_thread2_s2(self, s): self.label.setText(s) if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) window = MyWindow() window.setWindowTitle("Обмен сигналами между потоками") window.resize(300, 70) window.show() sys.exit(app.exec_())
В этом примере мы создали классы
Thread1
,
Thread2
и
MyWindow
. Первые два класса пред- ставляют собой потоки. Внутри них в методе run()
вызывается метод exec_()
, который за- пускает цикл обработки событий. В конструкторе класса
MyWindow производится создание надписи, кнопки и экземпляров классов
Thread1
и
Thread2
. Далее выполняется запуск сразу двух потоков.

Глава 17. Знакомство с PyQt 5 351
В следующей инструкции сигнал нажатия кнопки соединяется с методом on_start()
перво- го потока. Внутри этого метода производится какая-либо операция (в нашем случае — уве- личение значения атрибута count
), а затем с помощью метода emit()
генерируется сигнал s1
, и в параметре передается результат выполнения метода. Сигнал s1
соединен с методом on_change()
второго потока. Внутри этого метода также производится какая-либо операция, а затем генерируется сигнал s2
, и передается результат выполнения метода. В свою очередь сигнал s2
соединен со слотом on_thread2_s2
объекта окна, который выводит в надпись зна- чение, переданное с этим сигналом. Таким образом, при нажатии кнопки Сгенерировать сигнал вначале будет вызван метод on_start()
из класса
Thread1
, затем метод on_change()
из класса
Thread2
, а потом метод on_thread2_s2
класса
MyWindow
, который выведет результат выполнения на экран.
17.9.3. Модуль queue: создание очереди заданий
В предыдущем разделе мы рассмотрели возможность обмена сигналами между потоками.
Теперь предположим, что запущены десять потоков, которые ожидают задания в бесконеч- ном цикле. Как передать задание одному потоку, а не всем сразу? И как определить, какому потоку передать задание? Можно, конечно, создать список в глобальном пространстве имен и добавлять задания в этот список, но в этом случае придется решать вопрос о совместном использовании одного ресурса сразу десятью потоками. Ведь если потоки будут получать задания одновременно, то одно задание могут получить сразу несколько потоков, и какому- либо потоку не хватит заданий, — возникнет исключительная ситуация. Попросту говоря, возникает ситуация, когда вы пытаетесь сесть на стул, а другой человек одновременно пы- тается вытащить его из-под вас. Думаете, что успеете сесть?
Модуль queue
, входящий в состав стандартной библиотеки Python, позволяет решить эту проблему. Модуль содержит несколько классов, которые реализуют разного рода потоко- безопасные очереди. Опишем эти классы:

Queue
— очередь (первым пришел, первым вышел). Формат конструктора:
<Объект> = Queue([maxsize=0])
Пример:
>>> import queue
>>> q = queue.Queue()
>>> q.put_nowait("elem1")
>>> q.put_nowait("elem2")
>>> q.get_nowait()
'elem1'
>>> q.get_nowait()
'elem2'

LifoQueue
— стек (последним пришел, первым вышел). Формат конструктора:
<Объект> = LifoQueue([maxsize=0])
Пример:
>>> q = queue.LifoQueue()
>>> q.put_nowait("elem1")
>>> q.put_nowait("elem2")
>>> q.get_nowait()
'elem2'

352
Часть II. Библиотека PyQt 5
>>> q.get_nowait()
'elem1'

PriorityQueue
— очередь с приоритетами. Элементы очереди должны быть кортежами, в которых первым элементом является число, означающее приоритет, а вторым — значе- ние элемента. При получении значения возвращается элемент с наивысшим приоритетом
(наименьшим значением в первом параметре кортежа). Формат конструктора класса:
<Объект> = PriorityQueue([maxsize=0])
Пример:
>>> q = queue.PriorityQueue()
>>> q.put_nowait((10, "elem1"))
>>> q.put_nowait((3, "elem2"))
>>> q.put_nowait((12, "elem3"))
>>> q.get_nowait()
(3, 'elem2')
>>> q.get_nowait()
(10, 'elem1')
>>> q.get_nowait()
(12, 'elem3')
Параметр maxsize во всех трех случаях задает максимальное количество элементов, которое может содержать очередь. Если параметр равен нулю (значение по умолчанию) или отрица- тельному значению, то размер очереди не ограничен.
Эти классы поддерживают следующие методы:
 put(<Элемент>[, block=True][, timeout=None])
— добавляет элемент в очередь. Если в параметре block указано значение
True
, поток будет ожидать возможности добавления элемента, — при этом в параметре timeout можно указать максимальное время ожи- дания в секундах. Если элемент не удалось добавить, возбуждается исключение queue.Full
. В случае передачи параметром block значения
False очередь не будет ожи- дать, когда появится возможность добавить в нее новый элемент, и в случае невозмож- ности сделать это возбудит исключение queue.Full немедленно;
 put_nowait(<Элемент>)
— добавление элемента без ожидания. Эквивалентно: put(<Элемент>, False)
 get([block=True][, timeout=None])
— возвращает элемент, при этом удаляя его из оче- реди. Если в параметре block указано значение
True
, поток будет ожидать возможности извлечения элемента, — при этом в параметре timeout можно указать максимальное время ожидания в секундах. Если элемент не удалось получить, возбуждается исключе- ние queue.Empty
. В случае передачи параметром block значения
False очередь не будет ожидать, когда появится возможность извлечь из нее элемент, и в случае невозможности сделать это возбудит исключение queue.Empty немедленно;
 get_nowait()
1   ...   29   30   31   32   33   34   35   36   ...   83


написать администратору сайта