简体   繁体   中英

Trying to send b64encoded PNG image to JavaScript client via asyncio websockets server using Python

I am trying to send a base64 encoded PNG images to a JavaScript client via asyncio websockets server in python. Right now I am just looping through images from memory to create a "video player" on the JavaScript side and its working... however, the first image that comes through throws a RuntimeError on my python server. it then operates as expected so very hard to pinpoint the problem..

ERROR:asyncio:Task exception was never retrieved
future: <Task finished coro=<WebSocketCommonProtocol.send() done, defined at /usr/local/lib/python3.7/dist-packages/websockets/protocol.py:521> exception=RuntimeError('Task <Task pending coro=<WebSocketCommonProtocol.send() running at /usr/local/lib/python3.7/dist-packages/websockets/protocol.py:567> cb=[_wait.<locals>._on_completion() at /usr/lib/python3.7/asyncio/tasks.py:440] created at /usr/lib/python3.7/asyncio/tasks.py:361> got Future <Future pending> attached to a different loop') created at /usr/lib/python3.7/asyncio/tasks.py:361>
source_traceback: Object created at (most recent call last):
  File "/usr/lib/python3.7/threading.py", line 885, in _bootstrap
    self._bootstrap_inner()
  File "/usr/lib/python3.7/threading.py", line 917, in _bootstrap_inner
    self.run()
  File "/usr/lib/python3.7/threading.py", line 865, in run
    self._target(*self._args, **self._kwargs)
  File "../comm/mqtt.py", line 320, in __process_messages
    response_data = self._msg_callback_dispatch[t](in_msg.body)
  File "SiteManagerOp.py", line 385, in on_image_callback
    coro = asyncio.run(self.tx_UI_device_image(data.get('did'), data.get('img')), debug=True)
  File "/usr/lib/python3.7/asyncio/runners.py", line 43, in run
    return loop.run_until_complete(main)
  File "/usr/lib/python3.7/asyncio/base_events.py", line 571, in run_until_complete
    self.run_forever()
  File "/usr/lib/python3.7/asyncio/base_events.py", line 539, in run_forever
    self._run_once()
  File "/usr/lib/python3.7/asyncio/base_events.py", line 1767, in _run_once
    handle._run()
  File "/usr/lib/python3.7/asyncio/events.py", line 88, in _run
    self._context.run(self._callback, *self._args)
  File "Manager.py", line 181, in tx_UI_device_image
    await self.sockserver.send_to_clients(json.dumps(img_msg))
  File "/home/mendel/webserver/SocketServer.py", line 39, in send_to_clients
    await asyncio.wait([client.send(message) for client in self.clients])
  File "/usr/lib/python3.7/asyncio/tasks.py", line 361, in wait
    fs = {ensure_future(f, loop=loop) for f in set(fs)}
  File "/usr/lib/python3.7/asyncio/tasks.py", line 361, in <setcomp>
    fs = {ensure_future(f, loop=loop) for f in set(fs)}
Traceback (most recent call last):
  File "/usr/local/lib/python3.7/dist-packages/websockets/protocol.py", line 567, in send
    await self.write_frame(True, opcode, data)
  File "/usr/local/lib/python3.7/dist-packages/websockets/protocol.py", line 1077, in write_frame
    await self._drain()
  File "/usr/local/lib/python3.7/dist-packages/websockets/protocol.py", line 318, in _drain
    await self._drain_helper()
  File "/usr/local/lib/python3.7/dist-packages/websockets/protocol.py", line 299, in _drain_helper
    await waiter
RuntimeError: Task <Task pending coro=<WebSocketCommonProtocol.send() running at /usr/local/lib/python3.7/dist-packages/websockets/protocol.py:567> cb=[_wait.<locals>._on_completion() at /usr/lib/python3.7/asyncio/tasks.py:440] created at /usr/lib/python3.7/asyncio/tasks.py:361> got Future <Future pending> attached to a different loop

Python Code:

#storing images to memory..
img_data = []
i=1
while i < 300:
    with open(f"pngs/out{i}.png", "rb") as f:
        img_data.append(base64.b64encode(f.read()).decode('utf-8'))
        i+=1

#non-async call back for recieving images
def on_image_callback(self, data=None):
    print (f"rx image:", data.get('did'))
    coro = asyncio.run(self.tx_UI_device_image(data.get('did'), data.get('img')), debug=True) 

#async initiates send_to_client as JSON payload..
async def tx_UI_device_image(self, did, img):
    img_msg = {'image':{'did':did, 'img_data':img}}
    print ("img size: ", len(img))
    await self.sockserver.send_to_clients(json.dumps(img_msg))

#socket server handler...
class WebsocketServer:
    def __init__(self, host, port, onconnect_callback, rxdata_callback):
        self.start_server = websockets.serve(self.ws_handler, host, port)
        self.loop = asyncio.get_event_loop()
        self.onconnect = onconnect_callback
        self.ondata_Rx = rxdata_callback
        self.clients = set()

    def start(self):
        self.loop.run_until_complete(self.start_server)
        self.loop.run_forever()

    async def register(self, ws: WebSocketServerProtocol) -> None:
        self.clients.add(ws)
        logging.info(f'{ws.remote_address} connects.')
        await self.onconnect()
  
    async def unregister(self, ws: WebSocketServerProtocol) -> None:
        self.clients.remove(ws)
        logging.info(f'{ws.remote_address} disconnects.')

    async def send_to_clients(self, message: str) -> None:
        if self.clients:
            await asyncio.wait([client.send(message) for client in self.clients])
    
    async def ws_handler(self, ws: WebSocketServerProtocol, uri: str) -> None:
        await self.register(ws)
        try:
            await self.distribute(ws)
        finally:
            await self.unregister(ws)

    async def distribute(self, ws: WebSocketServerProtocol) -> None:
        async for message in ws:
            await self.ondata_Rx(json.loads(message))

JavaScript:

s.onmessage = function(e) {
    //console.log("got: " + e.data);
    var data = JSON.parse(e.data);
    for (var key in data) {
      if (data.hasOwnProperty(key)) { // this will check if key is owned by data object and not by any of it's ancestors
        if (key == "image"){
          console.log(data[key].did);
          var image = new Image();
          image.src =('data:image/png;base64,' + data[key].img_data);

          var player = document.getElementById(data[key].did + '-player');
          player.setAttribute('src', image.src)
        }
    }
}

I believe the problem is the asyncio.run call in a different thread affecting the event_loop or creating a new one rather, but I can't find any other way around this. It works with a much smaller message payload or if i just send a bunch of chars like ('A' * 300000)

What is causing this error and how do I resolve or workaround?

UPDATE: Found Solution

instead of calling asyncio.run() anytime i needed to call something outside of an async function - I used asyncio.run_coroutine_threadsafe(<coroutine>, loop=self.socketserver.loop)

不要在任何时候调用 async 函数之外的东西(或即在不同的线程中)调用asyncio.run() - 改用它并指定要定位的事件循环:

asyncio.run_coroutine_threadsafe(<coroutine>, loop=self.socketserver.loop)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM