run()的作用是启动 运行协程所需的环境(并在main()完成后将其关闭)。但在IPython中,一开始运行就已经自动帮你启动好了,所以可以直接用await(而且也不必把所有协程都放在一个主协程中,而可以散布在程序各处)。
create_task(sayhi(1)) 表示为协程sayhi(1)在某个“任务池”中创建一个任务,并且开始执行该任务。返回值是这个任务的handle,或者说“遥控器”。
任务池中的任务会并发地执行。任务在何时可以中断并切换到别的任务,这一点由await指定。
await sayhi1 有两重含义:
阻塞当前协程(该语句所在的协程,这里是main())的执行,直到任务sayhi1完成。(类似Process.join())
告诉解释器,现在当前协程(该语句所在的协程)开始阻塞,你可以切换协程了。
如果这里await的不是sayhi1而是,比如说,一个接受http请求的操作,那么在解释器切换协程后不会影响对这个请求的等待。这就是asyncio的强大之处。
这一点在await asyncio.sleep(delay)就有体现。asyncio.sleep()就具有“切换协程不影响等待”的特性。
关于await的几件事:
await的可以不是已创建的任务而是一个协程对象(比如await sayhi(1)),此时不会将其加入任务池,而会直接开始执行(当然,也可能刚开始执行就被切换到别的协程,因为用了await),并一直阻塞直到完成。这会导致sayhi(1)无法作为一个任务、与其他任务平等地参与并发,但是它仍然可以随着父协程(这里是main())的中断和恢复而间接地参与并发。
能够被await的不只有协程对象和任务handle,还有任何awaitable object,即任何实现了__await__方法(从而告诉了解释器如何在自己刚开始执行时就阻塞并切换协程,且不影响内部可能在进行的等待和其他操作)的对象。
await 的对象只可能在刚开始执行时立刻阻塞并切换协程。执行过程中其他可以阻塞的位置,是由这个对象内部使用的其他await语句指定的,而不是调用这个对象的那条await语句。
import time import asyncio start = time.perf_counter() async def sayhi(delay): await asyncio.sleep(delay) print(f'hi! at {time.perf_counter() - start}') async def main(): await sayhi(1) await sayhi(2) # asyncio.run(main()) # use outside IPython await main() # use inside IPython hi! at 1.0072715999995125 hi! at 3.0168006000021705 wait_for()把await A(A为任意awaitable)改成await asyncio.wait_for(A,timeout),就可以给await操作加上timeout秒的时限,一旦await了这么多秒还没有结束,就会中断A的执行并抛出asyncio.TimeoutError。
不用关心wait_for具体做了什么,你只需要记住await asyncio.wait_for(A,timeout)这个句子就行。可以认为这个句子和await A在(除了timeout以外的)其他方面上没有区别。下面是例子。
import time import asyncio async def eternity(): await asyncio.sleep(3600) print('yay!') async def main(): try: await asyncio.wait_for(eternity(), timeout=1.0) except asyncio.TimeoutError: print('timeout!') # asyncio.run(main()) # use outside IPython await main() # use inside IPython timeout! import time import asyncio start = time.perf_counter() async def sayhi(delay): await asyncio.sleep(delay) print(f'hi! at {time.perf_counter() - start}') async def main(): sayhi1 = asyncio.create_task(sayhi(1)) sayhi2 = asyncio.create_task(sayhi(2)) await asyncio.wait_for(sayhi1,1.05) await asyncio.wait_for(sayhi2,1.05) # asyncio.run(main()) # use outside IPython await main() # use inside IPython hi! at 1.0181081000046106 hi! at 2.0045300999918254 import time import asyncio start = time.perf_counter() async def sayhi(delay): await asyncio.sleep(delay) print(f'hi! at {time.perf_counter() - start}') async def main(): sayhi1 = asyncio.create_task(sayhi(1)) sayhi2 = asyncio.create_task(sayhi(2)) await asyncio.wait_for(sayhi1,0.95) await asyncio.wait_for(sayhi2,1.05) # asyncio.run(main()) # use outside IPython await main() # use inside IPython --------------------------------------------------------------------------- TimeoutError Traceback (most recent call last) <ipython-input-89-7f639d54114e> in <module> 15 16 # asyncio.run(main()) # use outside IPython ---> 17 await main() # use inside IPython <ipython-input-89-7f639d54114e> in main() 11 sayhi1 = asyncio.create_task(sayhi(1)) 12 sayhi2 = asyncio.create_task(sayhi(2)) ---> 13 await asyncio.wait_for(sayhi1,0.95) 14 await asyncio.wait_for(sayhi2,1.05) 15 ~\anaconda3\lib\asyncio\tasks.py in wait_for(fut, timeout, loop) 488 # See https://bugs.python.org/issue32751 489 await _cancel_and_wait(fut, loop=loop) --> 490 raise exceptions.TimeoutError() 491 finally: 492 timeout_handle.cancel() TimeoutError: hi! at 2.0194762000028277另外,注意到即使协程sayhi1抛出了异常,父协程main()仍然能够继续执行sayhi2。可见不同协程间是有一定的独立性的。
实现生产者-消费者协程