我有一个异步应用程序,该应用程序使用来自aiohttp
的服务器和带有asyncio.open_connection()
的异步套接字
我的代码包含来自PIL库的一些阻止调用,例如
Image.save()
Image.resize()
os.path.join()
被认为还可以吗?如何处理numpy
数组?答案 0 :(得分:3)
如果我使用这些阻止呼叫,我的Web服务器可以冻结吗?更多 确切地说,事件循环是否有可能错过事件,因为 阻止代码?
服务器将精确冻结执行图像功能的时间。您不会错过任何事件,但是在执行图像功能时,所有事件处理都会延迟。
冻结事件循环是一种糟糕的情况-您应该避免这种情况。
如果是,这些功能的替代品是什么? 与异步?没有异步版本的PIL。
避免冻结事件循环的最简单,通用的方法-使用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约束的操作,进程池可能并不总是一个好的解决方案。如果图像功能不需要花费很多时间(特别是如果服务器没有多个处理器核心),则在线程中运行它们可能仍然会更有效率。
通常,异步中什么被视为“阻止代码”?除了 显而易见的操作,例如套接字,读取文件等。例如, os.path.join()被认为还可以吗?那如何处理一个numpy数组呢?
粗略地说,任何函数都在阻塞:它会阻塞事件循环一段时间。但是像os.path.join
这样的许多函数只花费很少的时间,所以它们不是问题,我们也称它们为“阻塞”。
当执行时间(和事件循环冻结)成为问题时,很难说确切的限制,尤其是考虑到此时间对于不同的硬件将有所不同。我有偏见的建议-如果您的代码在将控制权返回事件循环之前花了50毫秒(或可能要花50毫秒以上),请考虑将其阻塞并使用run_in_executor
。
更新:
谢谢,使用一个(主线程的)事件循环有意义吗? 并使用另一个将使用同一循环添加任务的线程?
我不确定您的意思,但我认为不是。我们需要另一个线程来运行一些作业,而不是在其中添加任务。
在线程之后,我需要某种方式让线程通知主线程 图像处理完成。
只需等待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 response immediately,
# read https://stackoverflow.com/a/37345564/1113207
return web.Response(text="Image processed without blocking other requests")