首页 文章

使用asyncio进行PIL和阻止调用

提问于
浏览
2

我有一个asyncio应用程序使用 aiohttp 的服务器和 asyncio.open_connection() 的异步套接字

我的代码包含来自PIL库的一些阻塞调用

Image.save()
Image.resize()
  • 即使呼叫没有阻塞太长时间,但是,如果我使用这些阻止呼叫,我的网络服务器是否会冻结?更确切地说,由于阻塞代码,事件循环是否可能会丢失事件?

  • 如果是,那些与asyncio集成的功能的替代品是什么? PIL没有asyncio版本 .

  • 一般来说,在asyncio中被认为是'blocking code'?除了明显的操作,如套接字,读取文件等 .
    例如, os.path.join() 被认为是好的吗?在 numpy 阵列上工作怎么样?

1 回答

  • 3

    如果我使用这些阻止呼叫,我的网络服务器可以冻结吗?更确切地说,由于阻塞代码,事件循环是否可能会丢失事件?

    服务器将精确冻结执行图像功能的时间 . 您不会错过任何事件,但所有事件处理将在图像功能执行时延迟 .

    冻结事件循环是一个糟糕的情况 - 你应该避免它 .

    如果是,那些与asyncio集成的功能的替代品是什么? PIL没有asyncio版本 .

    避免冻结事件循环的最简单和通用的方法 - 使用asyncio.run_in_executor在另一个线程或另一个进程中执行阻塞功能 . 代码片段显示了如何执行此操作并包含有关何时使用进程或线程的详细说明:

    def blocking_io():
        # File operations (such as logging) can block the
        # event loop: run them in a thread pool.
        with open('/dev/urandom', 'rb') as f:
            return f.read(100)
    
    def cpu_bound():
        # CPU-bound operations will block the event loop:
        # in general it is preferable to run them in a
        # process pool.
        return sum(i * i for i in range(10 ** 7))
    

    我只想补充说,对于每个CPU绑定操作,进程池可能并不总是很好的解决方案 . 如果您的映像功能不需要花费太多时间(特别是如果您的服务器没有多个处理器核心),那么在线程中运行它们可能仍然会更高效 .

    一般来说,什么被认为是asyncio中的“阻止代码”?除了明显的操作,如套接字,读取文件等 . 例如,os.path.join()被认为是好的吗?在numpy阵列上工作怎么样?

    粗略地说任何函数都是阻塞的:它会阻塞事件循环一段时间 . 但像 os.path.join 这样的许多功能花费的时间很少,所以他们称之为"blocking" .

    当执行时间(和事件循环冻结)成为问题时,很难说准确的限制,特别是考虑到不同硬件的时间会有所不同 . 我有偏见的建议 - 如果您的代码在将控制权返回到事件循环之前需要(或可能需要)> 50 ms,请考虑阻止并使用 run_in_executor .

    Upd:

    谢谢,使用一个事件循环(主线程)和使用另一个使用相同循环添加任务的线程是否有意义?

    我不确定你的意思,但我想不是 . 我们需要另一个线程来运行一些工作,而不是在那里添加任务 .

    我需要一些方法让线程在图像处理完成后通知主线程

    等待 run_in_executor 的结果或用它开始任务 . run_in_executor - 是一个协程,它在后台线程中执行某些操作而不阻塞事件循环 .

    它看起来像这样:

    thrad_pool = ThreadPoolExecutor()
    
    
    def process_image(img):
        # all stuff to process image here
        img.save()
        img.resize()
    
    
    async def async_image_process(img):
        await loop.run_in_executor(
            thread_pool, 
            partial(process_image, img)
        )
    
    
    async def handler(request):
    
        asyncio.create_task(
            async_image_process(img)
        )
        # we use task to return responce imideately,
        # read https://stackoverflow.com/a/37345564/1113207
    
        return web.Response(text="Image processed without blocking other requests")
    

相关问题