Issue
I'm trying to make a UI which communicates in the background with several BLE devices. For that I've implemented a separate thread which runs an asyncio.loop. This is necessary because I use bleak 0.9.1 to connect to the devices.
Using signals and slots to get data from the UI-thread to the worker thread works fine. However, it does not work in the other direction. As far as I know this is because the thread is busy running the loop and never stops doing that. Therefore, it cannot process the inputs from the UI-thread.
Below there is an example code which shows the problem.
Is there any way to process the input slots in the thread while running the asyncio loop?
import sys
from PyQt5.QtWidgets import QWidget, QPushButton, QApplication, QVBoxLayout
from PyQt5.QtCore import QThread, QObject, pyqtSignal, pyqtSlot
import asyncio
class Test_Thread(QObject):
signal_back = pyqtSignal(int)
def __init__(self,
loop: asyncio.AbstractEventLoop,
parent=None):
super(Test_Thread, self).__init__(parent)
self.text = "Task1 not configured"
self.loop = loop
self.counter = 0
@pyqtSlot(str)
def set_text_slot(self, txt):
self.text = txt
async def do_stuff1(self):
while True:
print(self.text)
await asyncio.sleep(2.0)
async def do_stuff2(self):
while True:
self.counter += 1
self.signal_back.emit(self.counter)
await asyncio.sleep(1.0)
def work(self):
#run the event loop
try:
asyncio.ensure_future(self.do_stuff1(), loop=self.loop)
asyncio.ensure_future(self.do_stuff2(), loop=self.loop)
self.loop.run_forever()
finally:
print("Disconnect...")
class Window(QWidget):
set_text_signal = pyqtSignal(str)
def __init__(self, parent=None):
super(Window, self).__init__()
self.initUi()
self.startThread()
def initUi(self):
layout = QVBoxLayout()
self.button = QPushButton('User input')
self.button.clicked.connect(self.sendtotask)
layout.addWidget(self.button)
self.setLayout(layout)
self.show()
def startThread(self):
loop = asyncio.get_event_loop()
self.asyciothread = Test_Thread(loop)
self.thread = QThread()
self.asyciothread.moveToThread(self.thread)
self.set_text_signal.connect(self.asyciothread.set_text_slot)
self.asyciothread.signal_back.connect(self.receivefromthread)
self.thread.started.connect(self.asyciothread.work)
self.thread.start()
@pyqtSlot(int)
def receivefromthread(self, number):
print(str(number))
def sendtotask(self):
self.set_text_signal.emit("Task: Configured")
if __name__ == "__main__":
app = QApplication(sys.argv)
ui = Window()
ui.show()
sys.exit(app.exec_())
Solution
It is not necessary to use threads to use asyncio with Qt since there are libraries like asyncqt
and qasync
that enable it:
import asyncio
import sys
from PyQt5.QtWidgets import QWidget, QPushButton, QApplication, QVBoxLayout
from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot
from asyncqt import QEventLoop
# from qasync import QEventLoop
class Worker(QObject):
signal_back = pyqtSignal(int)
def __init__(self, loop: asyncio.AbstractEventLoop, parent=None):
super(Worker, self).__init__(parent)
self.text = "Task1 not configured"
self.loop = loop
self.counter = 0
@pyqtSlot(str)
def set_text_slot(self, txt):
self.text = txt
async def do_stuff1(self):
while True:
print(self.text)
await asyncio.sleep(2.0)
async def do_stuff2(self):
while True:
self.counter += 1
self.signal_back.emit(self.counter)
await asyncio.sleep(1.0)
def work(self):
asyncio.ensure_future(self.do_stuff1(), loop=self.loop)
asyncio.ensure_future(self.do_stuff2(), loop=self.loop)
class Window(QWidget):
set_text_signal = pyqtSignal(str)
def __init__(self, parent=None):
super(Window, self).__init__()
self.initUi()
self.start_task()
def initUi(self):
layout = QVBoxLayout(self)
self.button = QPushButton("User input")
self.button.clicked.connect(self.sendtotask)
layout.addWidget(self.button)
def start_task(self):
loop = asyncio.get_event_loop()
self.worker = Worker(loop)
self.set_text_signal.connect(self.worker.set_text_slot)
self.worker.signal_back.connect(self.receive_from_worker)
self.worker.work()
@pyqtSlot(int)
def receive_from_worker(self, number):
print(str(number))
def sendtotask(self):
self.set_text_signal.emit("Task: Configured")
if __name__ == "__main__":
app = QApplication(sys.argv)
loop = QEventLoop(app)
asyncio.set_event_loop(loop)
ui = Window()
ui.show()
with loop:
loop.run_forever()
Answered By - eyllanesc
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.