#python #multithreading #qt #pyqt #qthread
#python #многопоточность #qt #pyqt #qthread
Вопрос:
У меня есть приложение PyQt, которое получает информацию из внешнего источника с помощью обратных вызовов, которые вызываются из потоков, которые не находятся под моим контролем, а которые нет QThread
. Каков правильный способ передачи такой информации в основной поток без опроса? В частности, я хочу выдать сигнал Qt, чтобы я мог разбудить основной поток (или другой QThread
) при поступлении новых данных.
Комментарии:
1. Не уверен, что понимаю, разве вы не можете изменить исходный код потока, отличного от qt? как испускать сигнал по завершении?
2. Единственное, что находится под моим контролем, — это сама функция обратного вызова, которая является обычной функцией python. Т.Е. я мог бы излучать сигналы, но будет ли это разрешено? Поток, который будет запускать эту функцию, не создан моим кодом и вообще не имеет понятия о Qt. Так что, может быть, мне следует спросить, является ли передача сигналов потокобезопасной (даже из потоков, отличных от Qt)?
3. Я не знаю, является ли это лучшей практикой или плохой, но именно так я бы попытался сделать, отправить сигнал от вашего обратного вызова. Я думаю, это должно сработать. ( Безопасность потоков Qt зависит от типа соединения: doc.qt.io/qt-4.8 /…
4. В документах, которые вы связываете, указано: «С другой стороны, вы можете безопасно передавать сигналы из своей реализации QThread::run(), поскольку излучение сигнала является потокобезопасным». Во второй части говорится, что ваше предложение в порядке, но первая часть, похоже, снова ограничивает это утверждение QThreads…
5. Безопасность потоков означает, что он должен работать, даже если он не находится в
QThread
потоке на основе a.
Ответ №1:
Тип соединения по умолчанию для сигналов — Qt.AutoConnection, который в документах описывается следующим образом:
Если сигнал передается из другого потока, отличного от принимающего объекта, сигнал помещается в очередь и ведет себя как Qt::QueuedConnection. В противном случае слот вызывается напрямую, действуя как Qt::DirectConnection. Тип соединения определяется при отправке сигнала.
Поэтому перед отправкой сигнала Qt просто сравнивает текущую привязку потока отправителя и получателя, прежде чем решить, ставить его в очередь или нет. Не имеет значения, как изначально были запущены базовые потоки.
Вот простая демонстрация с использованием рабочего потока python:
import sys, time, threading
from PyQt4 import QtCore, QtGui
class Worker(object):
def __init__(self, callback):
self._callback = callback
self._thread = None
def active(self):
return self._thread is not None and self._thread.is_alive()
def start(self):
self._thread = threading.Thread(target=self.work, name='Worker')
self._thread.start()
def work(self):
print('work: [%s]' % threading.current_thread().name)
for x in range(5):
time.sleep(1)
self._callback(str(x))
class Window(QtGui.QPushButton):
dataReceived = QtCore.pyqtSignal(str)
def __init__(self):
super(Window, self).__init__('Start')
self.clicked.connect(self.start)
self.dataReceived.connect(self.receive)
self.worker = Worker(self.callback)
def receive(self, data):
print('received: %s [%s]' % (data, threading.current_thread().name))
def callback(self, data):
print('callback: %s [%s]' % (data, threading.current_thread().name))
self.dataReceived.emit(data)
def start(self):
if self.worker.active():
print('still active...')
else:
print('start: [%s]' % threading.current_thread().name)
self.worker.start()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = Window()
window.show()
print('show: [%s]' % threading.current_thread().name)
sys.exit(app.exec_())
Типичный вывод:
$ python test.py
show: [MainThread]
start: [MainThread]
work: [Worker]
callback: 0 [Worker]
received: 0 [MainThread]
still active...
callback: 1 [Worker]
received: 1 [MainThread]
still active...
callback: 2 [Worker]
received: 2 [MainThread]
still active...
callback: 3 [Worker]
received: 3 [MainThread]
callback: 4 [Worker]
received: 4 [MainThread]
Комментарии:
1. Вы говорите, что Qt «сравнил текущую привязку потока отправителя и получателя»: как определяется привязка потока для отправителя? Это фактический поток (например
threading.current_thread()
) или это поток, в котором находится отправляющийQObject
(который в вашем примере определяется приWorker
создании, что было бы неправильным потоком)? Первое имело бы больше смысла и сделало бы ваше решение правильным, в то время как последнее соответствует определению Qt doc «сходства потоков»: doc.qt.io/qt-5/qobject.html#thread-affinity2. Извините, моя ошибка, отправляющий объект в вашем примере на самом деле такой же, как и принимающий объект, это
Window
(как сигналdataReceived
, так и слотcallback
являются частьюWindow
). Таким образом, если бы он действительно использовал «привязку к потоку» в соответствии с документами, он (ошибочно) выполнил бы прямой вызов. Поэтому я делаю вывод, что Qt действительно сравнивает текущий запущенный поток (вместо привязки потока отправителя) к привязке потока получателя.3. @burnpanck. Да, «отправитель» означает рабочего в моем примере, потому что это то, что на самом деле вызывает функцию обратного вызова (на которую она ссылается).
Ответ №2:
Я бы справился с этим так же, как если бы вы просто опрашивали внешнее устройство или библиотеку. Создайте отдельный рабочий поток, который обрабатывает обратный вызов и отправляет сигнал в основной поток GUI.
class Worker(QObject):
data_ready = pyqtSignal(object, object)
def callback(self, *args, **kwargs):
self.data_ready.emit(args, kwargs)
class Window(...)
def __init__(self):
...
self.worker = Worker(self)
# Connect worker callback function to your external library
library.register_callback(self.worker.callback) #??
...
# Mover worker to thread and connect signal
self.thread = QThread(self)
self.worker.data_ready.connect(self.handle_data)
self.worker.moveToThread(self.thread)
self.thread.start()
@pyqtSlot(object, object)
def handle_data(self, args, kwargs):
# do something with data
Комментарии:
1. Действительно ли поток
self.thread
нужен? Что он будет делать? ФункцияWorker.callback
, конечно, все равно будет выполняться в этом неизвестном чужом потоке, созданномlibrary
…