Telegram 机器人库 Telethon的时候发现他家的 API 接口走的都是 async。

这就来学一下 Python 的 async 和 await (官方文档),需要 Python 版本3.7以上。

import asyncio

async def f():
  print('start')
  await asyncio.sleep(1) # sleep 1s
  print('end')

asyncio.run(f())

f() 返回了一个协程对象,asyncio.run调用会执行这个对象,输出startend之间会等一秒钟。 asyncawait的语法类似js。

import asyncio

async def f():
  print('start')
  await asyncio.sleep(1) # sleep 1s
  print('end')

async def main():
  await f()
  await f()

asyncio.run(main())

这份代码会调用f()两次,但并不会并发(看起来也是这样),那怎样让两个f()同时执行呢?

import asyncio

async def f():
  print('start')
  await asyncio.sleep(1) # sleep 1s
  print('end')

async def main():
  await asyncio.gather(f(), f())

asyncio.run(main())

asyncio.gather() 可以并发执行一些协程对象。协程函数如果有返回值,也可以通过gather的返回值拿到。

asyncio.wait_for(f(), timeout=0.5) 可以限制等待的时长,到时间会抛出一个asyncio.TimeoutError异常。

多线程

如果有阻塞线程的行为的话,也可以用asyncio.to_thread(func)把在其他线程调用函数func这个事情包装成一个协程对象可等待对象。例如(Python >= 3.9):

import os, asyncio

ip = '101.6.?.%d'

def test(x):
  return os.system(f'ping {ip%x} -c 1 -W 1')

async def main():
  tasks = [asyncio.to_thread(test, x=i) for i in range(256)]
  results = await asyncio.gather(*tasks)
  aval_ip = [i for i in range(256) if results[i]==0]
  return aval_ip

asyncio.run(main())

os.system是阻塞的,利用gather可以并发扫描某个网段下哪些机器回复ping

ps. Telethon库提供了同步的接口,通过import sync启用。