什么是异步?在传统的顺序编程中,所有发送给解释器的指令都是一条一条执行的。此类代码的输出很容易可视化和预测。但是...假设您有一个脚本,该脚本从3个不同的服务器请求数据。有时,无论出于何种原因,对其中一台服务器的请求可能会意外地花费很长时间来执行。想象一下,从第二台服务器获取数据需要10秒。当您等待时,整个脚本实际上什么都不做。如果您可以编写一个脚本,不等待第二个请求,而是跳过它,开始执行第三个请求,然后返回到执行第二个请求之前停止的地方,会怎样呢?就是这样。您可以通过切换任务来最大程度地减少空闲时间。尽管如此,当您需要一个几乎没有I/O的简单脚本时,您还是不想使用异步代码。更重要的一件事是,所有代码都在一个线程中运行。所以如果你想让程序的一部分在后台执行,同时做其他事情,那是不可能的。准备开始下面是asyncio主要概念的最基本定义:协程——消费数据但不产生数据的生成器。Python2.5引入了一种新语法,可以将数据发送到生成器。我建议查看DavidBeazley的“ACuriousCourseonCoroutinesandConcurrency”以获得对协程的详细介绍。task——协程调度器。如果你看下面的代码,你会发现它只是让event_loop尽快调用它的_step,而_step只是调用协程的下一步。classTask(futures.Future):def__init__(self,coro,loop=None):super().__init__(loop=loop)...self._loop.call_soon(self._step)def_step(self):...尝试:...result=next(self._coro)exceptStopIterationasexc:self.set_result(exc.value)exceptBaseExceptionasexc:self.set_exception(exc)raiseelse:...self._loop.call_soon(self._step)事件循环——把思考它作为asyncio的中央执行者。现在让我们看看它们是如何组合在一起的。正如我之前提到的,异步代码在线程中运行。从上图可以看出:1.消息循环是在一个线程中执行的2.从队列中获取任务3.每个任务在协程中执行下一个动作4.如果一个协程中调用了另一个协程(await),会触发上下文切换,挂起当前协程,并保存场景环境(变量,状态),然后加载调用的协程5.如果协程执行到阻塞部分(blockingI/O,Sleep),当前协程会挂起,将控制权交还给线程的消息循环,然后消息循环继续执行队列中的下一个任务……以此类推6.队列中的所有任务都完成后执行后,消息循环返回第一个任务异步和同步代码对比下面我们来实际验证一下异步方式的有效性,我来对比两个python脚本,两个脚本除了sleep方法是一样的。在第一个脚本中,我将使用标准的time.sleep方法,在第二个脚本中使用异步方法asyncio.sleep。这里使用Sleep是因为它是展示异步方法如何执行I/O的最简单方法。使用同步睡眠方法的代码:importasyncioimporttimefromdatetimeimportdatetimeasyncdefcustom_sleep():print('SLEEP',datetime.now())time.sleep(1)asyncdeffactorial(name,number):f=1foriinrange(2,number+1):print('Task{}:Computefactorial({})'.format(name,i))awaitcustom_sleep()f*=iprint('Task{}:factorial({})is{}\n'.format(name,number,f))start=time.time()loop=asyncio.get_event_loop()tasks=[asyncio.ensure_future(factorial("A",3)),asyncio.ensure_future(factorial("B",4)),]循环。run_until_complete(asyncio.wait(tasks))loop.close()end=time.time()print("Totaltime:{}".format(end-start))脚本输出:TaskA:Computefactorial(2)SLEEP2017-04-0613:39:56.207479TaskA:Computefactorial(3)SLEEP2017-04-0613:39:57.210128TaskA:factorial(3)is6TaskB:Computefactorial(2)SLEEP2017-04-0613:39:58.210778TaskB:Computefactorial(3)SLEEP2017-04-0613:39:59.212510TaskB:Computefactorial(4)SLEEP2017-04-0613:40:00.217308TaskB:factorial(4)is24Totaltime:5.016386032104492使用异常Sleep的代码:importasyncioimporttimefromdatetimeimportdatetimeasyncdefcustom_sleep():print('SLEEP{}\n'.format(datetime.now()))awaitasyncio.sleep(1)asyncdeffactorial(name,number):f=1foriinrange(2,number+1):print('Task{}:Computefactorial({})'.format(name,i))awaitcustom_sleep()f*=iprint('Task{}:factorial({})is{}\n'.format(name,number,f))start=time.time()loop=asyncio.get_event_loop()tasks=[asyncio.ensure_future(factorial("A",3)),asyncio.ensure_future(factorial("B",4)),]循环。run_until_complete(asyncio.wait(tasks))loop.close()end=time.time()print("Totaltime:{}".format(end-start))脚本输出:TaskA:Computefactorial(2)SLEEP2017-04-0613:44:40.648665TaskB:Computefactorial(2)SLEEP2017-04-0613:44:40.648859TaskA:Computefactorial(3)SLEEP2017-04-0613:44:41.649564TaskB:Computefactorial(3)SLEEP2017-04-0613.49:49:49:4TaskA:factorial(3)is6TaskB:Computefactorial(4)SLEEP2017-04-0613:44:42.651755TaskB:factorial(4)is24Totaltime:3.008226156234741从输出可以看到,异步模式的代代码的执行速度大约快了两秒。使用异步模式时(每次调用awaitasyncio.sleep(1)),流程控制都会回到主程序的消息循环,开始运行队列中的其他任务(任务A或任务B)。当使用标准的sleep方法时,当前线程会挂起等待。什么都不做。其实在标准的sleep过程中,当前线程也会返回一个python解释器,可以操作其他已经存在的线程,不过这是另外一个话题了。推荐使用异步模式编程的几个原因很多公司都在产品中广泛使用了异步模式,比如Facebook著名的ReactNative和RocksDB。像每天可以承载50亿用户访问量的推特,也是依赖于异步模式编程。那么,通过重构代码,或者改变mode方法,可以让系统运行得更快,何乐而不为呢?