pythonpython-asynciosanic

Python async await not waiting


I have not worked much with async python, but currently i have a project with Sanic framework. There is websocket endpoint, which receives data from client, sends message to client, that task have been started, then runs long sync task (there is no options to make it async) and finally sends message to client that task is done. There is some example code:

from sanic import Sanic
from sanic.server.protocols.websocket_protocol import WebSocketProtocol

app = Sanic("TestApp")

@app.websocket("/")
async def process_task(request, ws: WebSocketServerProtocol):
    raw_data = await ws.recv()
    data = json.loads(raw_data)
    await ws.send("TASK STARTED")
    process_long_task(data) # Long sync function
    await ws.send("TASK ENDED")

But there is a problem. Function does not wait for ws.send("TASK STARTED"). Actually both messages are send only after process_long_task finishes. Although, it's working correctly if i add await asyncio.sleep(0.1) after await ws.send("TASK STARTED")

Can someone point out to me what's wrong with my code?


Solution

  • If you call a synchronous function from a coroutine the entire loop and all tasks running inside it will be waiting for that single synchronous function to complete.

    If you need to have a long running synchronous function, and await asynchronously for the result there are a few ways, most people use a background thread of some kind.

    The built in method uses a thread pool and asyncio built in run_in_executor which pretty much creates a future and awaits on it while the background thread from the threadpool completes the function call, then it puts the result into the future and flags that its done.

    Here is an example of socket.gethostbyaddr() (a sometimes very slow to finish synchronous function) being used asyncronously.

    import socket
    import concurrent.futures
    import asyncio
    
    pool = concurrent.futures.ThreadPoolExecutor()
    
    async def GetHostFromAddr(ip):
        """
        GetHostFromAddr(ip) -> fqdn\n
        :param ip: The hosts ip address ie. 192.168.1.1
        :return: Return the fqdn (a string of the form 'sub.example.com') for a host.
        """
        loop = asyncio.get_running_loop()
        
        # ThreadPool, function, arguments
        return await loop.run_in_executor(pool, socket.gethostbyaddr, ip)