【问题标题】:How to wait 5 seconds (without blocking) before sending Response?如何在发送响应之前等待 5 秒(不阻塞)?
【发布时间】:2018-05-08 04:24:17
【问题描述】:

这似乎是可能的,因为在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()

【问题讨论】:

    标签: python-asyncio sanic


    【解决方案1】:

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

    • raise500 会阻止其他 raise500(错误)

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

    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)

    这是因为你在 awaitable 函数中返回了 awaitable,但 sanic await 只针对第一个:

    @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. 删除 ret500 的 async

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

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

    【讨论】:

      【解决方案2】:

      实现__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()
      

      【讨论】:

      • 它在某些条件下部分工作,我用我的观察编辑了 OP
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-11-22
      • 1970-01-01
      • 2018-11-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多