Я пытаюсь создать новое приложение на основе PyQt5 и asyncio (с python 3.4, с нетерпением жду, чтобы в итоге обновить до 3.5 с помощью async/await). Моя цель - использовать asyncio, чтобы графический интерфейс оставался отзывчивым, даже когда приложение ждет, когда какое-то подключенное оборудование завершит операцию.PyQt5 и asyncio: выход из никогда не заканчивается
Когда вы смотрите, как объединить циклы событий Qt5 и asyncio, я нашел mailing list posting, предлагая использовать quamash. Однако при выполнении этого примера (немодифицированный)
yield from fut
Невер, похоже, возвращается. Я вижу выход «Тайм-аут», поэтому обратный вызов таймера, очевидно, срабатывает, но будущее не может разбудить метод ожидания. При ручном закрытии окна, он говорит мне, что есть незавершенные фьючерсы:
Yielding until signal...
Timeout
Traceback (most recent call last):
File "pyqt_asyncio_list.py", line 26, in <module>
loop.run_until_complete(_go())
File "/usr/local/lib/python3.5/site-packages/quamash/__init__.py", line 291, in run_until_complete
raise RuntimeError('Event loop stopped before Future completed.')
RuntimeError: Event loop stopped before Future completed.
Я испытал это на Ubuntu с питоном 3.5 и на Windows, с 3.4, такое же поведение на обеих платформах.
Во всяком случае, так как это не то, что я на самом деле пытаются достичь, я испытал некоторый другой код, а также:
import quamash
import asyncio
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
@asyncio.coroutine
def op():
print('op()')
@asyncio.coroutine
def slow_operation():
print('clicked')
yield from op()
print('op done')
yield from asyncio.sleep(0.1)
print('timeout expired')
yield from asyncio.sleep(2)
print('second timeout expired')
def coroCallHelper(coro):
asyncio.ensure_future(coro(), loop=loop)
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
def btnCallback(obj):
#~ loop.call_soon(coroCallHelper, slow_operation)
asyncio.ensure_future(slow_operation(), loop=loop)
print('btnCallback returns...')
btn = QPushButton('Button', self)
btn.resize(btn.sizeHint())
btn.move(50, 50)
btn.clicked.connect(btnCallback)
self.setGeometry(300, 300, 300, 200)
self.setWindowTitle('Async')
self.show()
with quamash.QEventLoop(app=QApplication([])) as loop:
w = Example()
loop.run_forever()
#~ loop = asyncio.get_event_loop()
#~ loop.run_until_complete(slow_operation())
Программа должна отображать окно с кнопкой в нем (что он делает) , при нажатии кнопки slow_operation() без блокировки GUI. При запуске этого примера я могу нажимать кнопку так часто, как хочу, поэтому графический интерфейс не блокируется. Но
yield from asyncio.sleep(0.1)
никогда не передается и терминал вывода выглядит следующим образом:
btnCallback returns...
clicked
op()
op done
btnCallback returns...
clicked
op()
op done
Там не исключение брошено, когда я закрыть окно на этот раз. Функция slow_operation() в основном работает, если я непосредственно запустить цикл событий с ним:
#~ with quamash.QEventLoop(app=QApplication([])) as loop:
#~ w = Example()
#~ loop.run_forever()
loop = asyncio.get_event_loop()
loop.run_until_complete(slow_operation())
Теперь два вопроса:
Это разумный способ достичь развязку длительных операций с помощью графического интерфейса , в общем? Мое намерение заключается в том, что обратный вызов кнопки отправляет вызов coroutine в цикл событий (с дополнительным уровнем вложенности или без него, cf. coroCallHelper()), где он затем запланирован и выполнен. Мне не нужны отдельные потоки, так как на самом деле только входы/выходы занимают время, а не фактическая обработка.
Как я могу исправить это поведение?
Спасибо, Philipp
Да. По умолчанию 'asyncio.get_event_loop()' возвращает значение по умолчанию 'SelectorEventLoop'. В 'asyncio' есть много функций, которые используют' get_event_loop() 'для получения цикла и присоединения к нему обратных вызовов. Если вы не задаете «set_event_loop» свой настраиваемый цикл событий, все фьючерсы и сопрограммы получат запланированный цикл событий по умолчанию, который никогда не запускается. –
Спасибо за объяснение, теперь это имеет смысл. –
@PhilippBurch не возражаете принять свой собственный ответ, это совершенно нормально на SO/SE, чтобы сделать это. – Killah