asyncio仅单任务运行:多SFTP连接文件上传异常排查
问题排查与解决方案
核心原因分析
你遇到的问题本质是协程没有真正让出CPU控制权,导致事件循环无法切换到其他协程,常见原因有两个:
1. 使用了同步SFTP库(如paramiko)
如果你的代码里用的是paramiko这类同步SFTP客户端,哪怕把它放到asyncio协程里调用,它的write等IO操作都是阻塞式的同步调用——这些操作不会触发asyncio的协程切换,会一直占着事件循环线程,直到整个操作完成。这就导致其他协程根本没机会运行。
2. SHA256计算是同步CPU密集操作
hashlib的update方法是纯CPU操作,属于同步阻塞任务。asyncio是单线程事件循环,一旦某个协程在执行CPU密集任务,会一直占用线程,不会主动让出控制权,其他协程只能等这个任务做完才能运行。
针对性解决方法
替换为异步SFTP库
放弃同步的paramiko,改用支持asyncio的异步SFTP库,比如asyncssh。它的所有IO操作都是原生异步的,调用await方法时会自动让出事件循环,让其他协程有机会执行。示例代码片段:
import asyncio import asyncssh import hashlib async def upload_chunk(sftp, file_path, chunk, digester): # 异步SFTP写入,会触发协程切换 await sftp.write(file_path, chunk, append=True) # 将SHA256计算放到线程池避免阻塞事件循环 await asyncio.get_event_loop().run_in_executor(None, digester.update, chunk) async def main(): async with asyncssh.connect('sftp.example.com', username='user', password='pass') as conn: async with conn.start_sftp_client() as sftp: # 开启多个独立SFTP连接(或复用连接的多个协程) tasks = [] digester = hashlib.sha256() with open('large_file.bin', 'rb') as f: while chunk := f.read(1024*1024): tasks.append(upload_chunk(sftp, '/remote/path/file.bin', chunk, digester)) await asyncio.gather(*tasks) print(f"SHA256: {digester.hexdigest()}") # Python 3.6需用以下方式启动 loop = asyncio.get_event_loop() loop.run_until_complete(main())
把CPU密集的SHA256计算放到线程池
如果必须保留现有SFTP库(或无法切换),或者SHA256计算是瓶颈,需要用run_in_executor把CPU密集任务放到线程池执行,避免阻塞事件循环:
# 替换同步的digester.update(chunk)为: await asyncio.get_event_loop().run_in_executor(None, digester.update, chunk)
这样SHA256计算会在后台线程执行,当前协程会让出控制权,事件循环可以调度其他协程。
确认事件循环的正确运行
确保你是用asyncio.gather(*tasks)来同时运行多个upload协程,而不是串行等待每个协程完成。比如:
- 错误写法(串行执行):
for task in tasks: await task
- 正确写法(并行调度):
await asyncio.gather(*tasks)
额外注意事项
- Python 3.6的
asyncio没有asyncio.run(),需要用loop = asyncio.get_event_loop(); loop.run_until_complete(main())代替。 - 多个SFTP连接要注意服务器的连接数限制,避免被服务器封禁。
内容的提问来源于stack exchange,提问作者Mikhail T.
相关产品推荐
相关产品推荐

