为何AWS Lambda异步实现的CloudWatch日志仍呈同步顺序?
问题原因及解决方案
核心原因
你的异步代码只是套了async函数的外壳,但核心操作全是同步阻塞的,根本没用到asyncio的异步调度能力,所以日志和同步版本完全一致。
具体来说:
_download_image是同步调用,会直接阻塞事件循环,直到下载完成才会继续执行后续代码。monochrome_and_upload和brighten_and_upload如果是同步函数(比如用了boto3同步SDK、同步图像处理库),就算放到asyncio.gather里,也会同步执行——因为asyncio无法调度普通同步函数,会直接把它们当成阻塞任务从头到尾跑完,不会切换到其他协程。- asyncio的并发是基于协程挂起的,只有当代码遇到
await(等待异步操作)时,事件循环才会切换到其他协程执行。你的代码里没有任何真正的异步等待逻辑,自然和同步代码没区别。
解决步骤
替换异步IO SDK
把同步的boto3换成异步版本aioboto3,所有S3下载/上传操作都改成await调用的异步方法,示例:async def _download_image(self, image_key, local_path): async with aioboto3.client('s3') as s3: await s3.download_file(self.bucket_name, image_key, local_path)包装同步阻塞操作
如果暂时不想换SDK,或者图像处理是CPU密集型/本地文件操作这类同步任务,用asyncio.to_thread(Python3.9+)或者loop.run_in_executor把同步操作包装成可await的协程,示例:# 下载图片改成异步调用 await asyncio.to_thread(self._download_image, image_key, temp_image_path + "." + image_file_suffix) # 图像处理任务也包装成可await的 tasks = [ asyncio.to_thread(self.bw_image_processor.monochrome_and_upload, temp_image_path + "." + image_file_suffix), asyncio.to_thread(self.brighten_image_processor.brighten_and_upload, temp_image_path + "." + image_file_suffix) ] await asyncio.gather(*tasks)注:Python3.8没有
asyncio.to_thread,可以用以下代码替代:loop = asyncio.get_running_loop() await loop.run_in_executor(None, self._download_image, image_key, temp_image_path + "." + image_file_suffix)确保协程函数正确定义
所有需要异步调度的方法都要定义成async def,并且内部的阻塞操作都要通过await或者线程/进程池包装,不能直接调用同步阻塞函数。
额外说明
Lambda的Python运行环境是单线程事件循环,只有IO密集型的异步操作能发挥并发优势;如果是CPU密集型的图像处理,更适合用多进程(比如concurrent.futures.ProcessPoolExecutor),避免阻塞事件循环。
内容的提问来源于stack exchange,提问作者SSM Tariq
相关产品推荐
相关产品推荐

