2

I have a websockets server running in python, and also wanted to show some GUI using PyQt5. Both websockets and PyQt5 has to run forever in the same thread, but i'm not sure how i can do it.

below is the code i implement to create and start the server.

import websockets
import asyncio


async def server_extentions(websocket, path):
    try:
        while (True):
            request = await websocket.recv()
            response = "blabla"
            await websocket.send(response)
    except websockets.ConnectionClosed as exp:
        print("connection closed.")

evt_loop = asyncio.get_event_loop()
start_server = websockets.serve(server_extentions, '127.0.0.1', 5588, loop=evt_loop)
try:
    evt_loop.run_until_complete(start_server)
    evt_loop.run_forever()
finally:
    evt_loop.run_until_complete(evt_loop.shutdown_asyncgens())
    evt_loop.close()

below is some gui i show in my application


from PyQt5.QtWidgets import QProgressBar, QWidget, QLabel, QApplication
from PyQt5 import QtCore


class DownloadProgress(QWidget):

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

    def init_ui(self):
        self.progress_bar = QProgressBar(self)
        self.progress_bar.setGeometry(30, 40, 200, 25)
        self.progress_bar.setValue(0)

        self.label_status_msg = QLabel()
        self.label_status_msg.setAlignment(QtCore.Qt.AlignCenter)
        self.label_status_msg.setGeometry(30, 80, 200, 25)
        self.label_status_msg.setText("starting")

        self.setGeometry(300, 300, 280, 170)
        self.setWindowTitle('Bla Bla')

    def set_progress_value(self, value: int):
        self.progress_bar.setValue(value)

    def set_status_msg(self, msg: str):
        self.label_status_msg.setText(msg)

app = QApplication([])


dp = DownloadProgress()
dp.set_status_msg("download in progress.")
dp.set_progress_value(20)
dp.show()

app.exec()


shower me some light, to run both the tasks in same event loop.

note: i definitely want them in same thread.

Thanks in advance.

Manthri Anvesh
  • 95
  • 2
  • 12
  • 1
    Use QtWebSockets, see https://doc.qt.io/qt-5/qtwebsockets-examples.html or https://stackoverflow.com/questions/35237245/how-to-create-a-websocket-client-by-using-qwebsocket-in-pyqt5 or look into QThread, see e.g. here: https://stackoverflow.com/questions/6783194/background-thread-with-qthread-in-pyqt – Joe Jun 13 '19 at 17:59
  • @Joe QtWebSockets does help, but here in my situation i cannot implement QtWebSockets, i have to stick to websockets only. therefore i'm looking for solution which can combine both. – Manthri Anvesh Jun 14 '19 at 08:49
  • So put the receiver part on the GUI side in a QTread. – Joe Jun 14 '19 at 09:00

1 Answers1

3

app.exec() runs QT's event loop, which you can't do since you have to run asyncio's event loop with run_forever().

Fortunately Qt provides processEvents which you can call periodically alongside asyncio's event loop to make Qt work:

You can call this function occasionally when your program is busy performing a long operation (e.g. copying a file).


Remove app.exec() and alter code following way:

app = QApplication([])

async def qt_loop():    
    while True:
        app.processEvents()     # allow Qt loop to work a bit
        await asyncio.sleep(0)  # allow asyncio loop to work a bit

qt_loop_task = asyncio.ensure_future(qt_loop())  # task will work alongside with the server


evt_loop = asyncio.get_event_loop()    
start_server = websockets.serve(server_extentions, '127.0.0.1', 5588, loop=evt_loop)
try:
    evt_loop.run_until_complete(start_server)
    evt_loop.run_forever()
finally:
    # I'll leave to you job to kill qt_loop_task 
    # see: https://stackoverflow.com/a/37345564/1113207
    evt_loop.run_until_complete(evt_loop.shutdown_asyncgens())
    evt_loop.close()

I didn't check it, but I presume it should work.

Good idea is to enable debug mode while testing to make sure everything is fine.

Mikhail Gerasimov
  • 36,989
  • 16
  • 116
  • 159
  • Thanks, it works well. but i would also like to know if there are any downsides for this approach. like when handling a lot of connection's requests and so on..? – Manthri Anvesh Jun 17 '19 at 06:20
  • @ManthriAnvesh I don't see much downsides. Few things come to mind. If `processEvents` will take long time to execute it'll freeze event loop, but function seems to be specially designed to return fast. Anyway, if you want to use more tested and time-proof approach you may be interested in trying [quamash](https://github.com/harvimt/quamash#quamash) or its fork [asyncqt](https://github.com/gmarull/asyncqt). – Mikhail Gerasimov Jun 17 '19 at 08:01
  • when i run the code, CPU is utilized to nearly 32% constantly [due to qt_loop() ]. is there away i can optimize on it. – Manthri Anvesh Jun 18 '19 at 13:53
  • @ManthriAnvesh try to change `asyncio.sleep(0)` with, let's say, `asyncio.sleep(0.01)`. – Mikhail Gerasimov Jun 18 '19 at 15:25
  • will it cause any lags in animations or transitions in UI. – Manthri Anvesh Jun 19 '19 at 04:16
  • @ManthriAnvesh it may, although I don't think they'll be distinguishable. You can try lower `0.01` to see how it affects UX and CPU. If you want better solution you should definitely check mentioned [asyncqt](https://github.com/gmarull/asyncqt) ([example](https://github.com/gmarull/asyncqt/blob/master/examples/aiohttp_fetch.py)). – Mikhail Gerasimov Jun 19 '19 at 08:58