如何在发送响应之前等待5秒(不阻塞)?

时间:2017-11-24 07:39:08

标签: python-asyncio sanic

这似乎是可能的,因为在app.Sanic.handle_request()中有snippet

            if isawaitable(response):
                response = await response

这就是Python检查awaitable的方式:

def isawaitable(object):
    """Return true if object can be passed to an ``await`` expression."""
    return (isinstance(object, types.CoroutineType) or
            isinstance(object, types.GeneratorType) and
                bool(object.gi_code.co_flags & CO_ITERABLE_COROUTINE) or
            isinstance(object, collections.abc.Awaitable))

我知道使用async def创建一个等待功能,但我不知道如何创建一个等待的HTTPResponse实例。如果可能的话,用简单的await asyncio.sleep(5)看一个等待响应的例子会很有帮助。

试过Mikhail的解决方案,这是我观察到的:

  • raise500输入asyncio.sleep()
  • ret500未输入asyncio.sleep()(错误)
  • raise500阻止其他raise500(错误)
  • raise500不会阻止ret500
  • 无法判断ret500是否会阻止其他ret500,因为它太快(没有睡觉)

完整代码(通过保存为test.py运行,然后在shell python test.py中运行并转到http://127.0.0.1:8000/api/test):

import asyncio
from sanic import Sanic
from sanic.response import HTTPResponse
from sanic.handlers import ErrorHandler


class AsyncHTTPResponse(HTTPResponse):  # make it awaitable
    def __await__(self):
        return self._coro().__await__()  # see https://stackoverflow.com/a/33420721/1113207

    async def _coro(self):
        print('Sleeping')
        await asyncio.sleep(5)
        print('Slept 5 seconds')
        return self


class CustomErrorHandler(ErrorHandler):
    def response(self, request, exception):
        return AsyncHTTPResponse(status=500)


app = Sanic(__name__, error_handler=CustomErrorHandler())


@app.get("/api/test")
async def test(request):
    return HTTPResponse(status=204)


@app.get("/api/raise500")
async def raise500(request):
    raise Exception


@app.get("/api/ret500")
async def ret500(request):
    return AsyncHTTPResponse(status=500)

if __name__ == "__main__":
    app.run()

2 个答案:

答案 0 :(得分:3)

实现__await__魔术方法的类变得等待。

我没有检查它是否适用于您的情况,但这是创建等待的自定义类实例的示例:

import asyncio
from inspect import isawaitable


class HTTPResponse:  # class we have
    pass


class AsyncHTTPResponse(HTTPResponse):  # make it awaitable
    def __await__(self):
        return self._coro().__await__()  # see https://stackoverflow.com/a/33420721/1113207

    async def _coro(self):
        await asyncio.sleep(2)
        return self


async def main():
    resp = AsyncHTTPResponse()

    if isinstance(resp, HTTPResponse):
        print('It is HTTPResponse class ...')

    if isawaitable(resp):
        print('... which is also awaitable.')

    print('Let us see how it works.')
    await resp


loop = asyncio.get_event_loop()
try:
    loop.run_until_complete(main())
finally:
    loop.run_until_complete(loop.shutdown_asyncgens())
    loop.close()

答案 1 :(得分:3)

由于米哈伊尔的回答是正确的,我只会讨论进一步的编辑

  
      
  • raise500阻止其他raise500(bug)
  •   

它似乎没有阻止。一个简单的测试(添加一些查询字符串来区分请求):

for i in `seq 2`;do curl http://127.0.0.1:8000/api/raise500&req=$i & done

从日志的日期时间看,请求之间没有延迟(阻止)

Sleeping
Sleeping
Slept 5 seconds
2017-11-26 01:01:49 - (network)[INFO][127.0.0.1:37310]: GET http://127.0.0.1:8000/api/raise500?req=1  500 0
Slept 5 seconds
2017-11-26 01:01:49 - (network)[INFO][127.0.0.1:37308]: GET http://127.0.0.1:8000/api/raise500?req=2  500 0
  
      
  • ret500不会进入asyncio.sleep()(bug)
  •   

这是因为你在等待的功能中等待,但是sanic只等到第一个:

@app.get("/api/ret500")
async def ret500(request):
    return AsyncHTTPResponse(status=500)

handle_request确实:

response = ret500(request)  # call `async def ret500` and returns awaitable
if isawaitable(response):
    response = await response  # resolve and returns another awaitable - AsyncHTTPResponse object

# note to wait 5 seconds sanic would need again await for it
# response = await response

解决方案:

  1. 不要返回等待,换句话说就是await AsyncHTTPResponse自己

    @app.get("/api/ret500")
    async def ret500(request):
        res = await AsyncHTTPResponse(status=500)
        return res
    
  2. drop ret500' async

    @app.get("/api/ret500")
    def ret500(request):
        return AsyncHTTPResponse(status=500)
    

    注意:此技术仅在不打算在其中调用异步函数时才有效。