asyncio模块做为一个临时的库,在Python 3.4版本中加入。这意味着,asyncio模块可能作不到向后兼容甚至在后续的Python版本中被删除。根据Python官方文档,asyncio经过coroutines、sockets和其它资源上的多路复用IO访问、运行网络客户端和服务端以及其它相关的原始服务等提供了一种单线程并发应用的架构。本文并不能覆盖全部关于asyncio模块的技术点,可是你能够学到如何去使用这个模块,以及为何它是有用的。python
若是你在一些较老的Python版本中须要一些相似于asyncio模块的技术,你能够看Twisted或者gevent。编程
asyncio模块提供了一种关于事件循环的框架。事件循环就是等待一些任务发生,而后执行相应的事件。它也会处理例如IO操做或者系统事件。asyncio实际中有好几种循环实现方式。模块默认使用的方式是其所运行的操做系统上最有效的方式。若是你愿意,你也能够显式地选择其它事件循环方式。一个事件循环就是当事件A发生时,函数B共同起做用。安全
设想这样一个场景,服务器等待用户访问并请求一些资源,例如网页。若是这个网站不是很是知名的网站,这个服务器将会在很长的时间内处于空闲状态。可是,一旦某个时间用户点击了这个网站,服务器就须要做出响应。这个响应就是事件处理。当一个用户下载网页,服务器将会去检查并调用一个或者多个事件句柄。一旦这些事件句柄完成相应的处理,它们须要将控制交回给事件循环。为了在Python中完成这个任务,asyncio使用协程。服务器
协程是一个特殊的函数,能够将控制交回给它的调用函数,可是并不丢失它的状态。协程是一个消费者函数,而且是生成器的扩展。协程相比线程最大的优点就是执行协程时不须要占用太多内存。你须要注意的是,当你调用一个协程函数,它并无真正执行。相反,它将会返回一个协程对象,你能够将这个协程对象传递给事件循环,而后能够当即或者稍后执行它。网络
当你在使用asyncio模块时,另外一个你可能会执行的是future。future就是一个能够表示尚未结束的任务结果的对象。你的事件循环能够观察future对象并等待它们结束。当一个future结束时,它被设置为已完成。asyncio模块也支持锁和信号。架构
本文最后一部分,我将会提到Task。Task是协程的一个框架,是Future的一个子类。你能够在事件循环中对Task进行调度。并发
async和await是Python 3.5中新添加的关键词,用来定义一个原生的协程,以便于和基于协程的生成器相区别。若是你想了解更多关于async和await的知识,你能够去阅读PEP 492。框架
在Python 3.4中,你能够按照以下方式建立一个协程,异步
import asyncio @asyncio.coroutine def my_foo(): yield from func()
这个装饰器在Python 3.5中依然有效,可是模块的类型有所更新,协程函数能够告诉你正在交互的是否是一个原生的协程。从Python 3.5开始,你可使用async def这种语法来定义一个协程函数,因此上述函数能够按照以下方式定义,socket
import asyncio async def my_coro(): await func()
当你以这种方式定义一个协程函数,你不能在函数内部使用yield。取而代之,你必须使用return或者await语句,用于将返回值返回给调用者。你须要注意的是,关键字await只能在async def函数中使用。
关键字async和await能够认为是异步编程中的接口。asyncio模块就是一个能够将async/await用于异步编程的框架。实际上,有一个叫作curio的项目证明了这个概念,那就是它单独实现了在后台使用async/await的事件循环。
尽管上述的描述可让你得到不少关于协程如何工做的背景知识,有时候,你仅仅想看到一些示例,这样你就能够切身感觉到它的语法形式,以及如何将这些代码组合在一块儿。考虑到这一点,让咱们以一个简单的示例开始把。
一个很是常见的任务就是你想完整的下载一个文件,这个文件可能来源于内部资源或者互联网。固然你想要下载的文件可能不止一个。让咱们建立两个协程来完成这个任务。
import asyncio import os import urllib.request async def download_coroutine(url): request = urllib.request.urlopen(url) filename = os.path.basename(url) with open(filename,"wb") as file_handle: while True: chunk = request.read(1024) if not chunk: break file_handle.write(chunk) msg = "Finished downloading {filename}".format(filename = filename) return msg async def main(urls): coroutines = [download_coroutine(url) for url in urls] completed,pending = awit asyncio.wait(coroutines) for item in completed: print(item.result()) if __name__ == "__main__": urls = ["http://www.irs.gov/pub/irs-pdf/f1040.pdf", "http://www.irs.gov/pub/irs-pdf/f1040a.pdf", "http://www.irs.gov/pub/irs-pdf/f1040ez.pdf", "http://www.irs.gov/pub/irs-pdf/f1040es.pdf", "http://www.irs.gov/pub/irs-pdf/f1040sb.pdf"] event_loop = asyncio.get_event_loop() try: event_loop.run_until_complete(main(urls)) finally: event_loop.close()
这段代码中,咱们引入了咱们须要的模块,而后经过async语法建立了第一个协程。这个协程叫作download_coroutine,它使用Python的urllib模块下载传递给它的任何URL地址。当它完成任务时,它将会返回一条相应的信息。
另外一个协程就是咱们的主协程。它基本上就是获取一个包含一个或者多个URL地址的列表,而后将它们加入队列。咱们使用asyncio的wait函数用于等待协程的结束。固然,为了启动这些协程,它们须要被加入到事件循环中。咱们在代码段中最后的地方作了这个处理,咱们先获取一个事件循环,而后调用它的run_until_complete的方法。你将会注意到,咱们将主协程传入事件循环中。这个会先运行主协程,主协程将第二个协程加入到队列中,并让它们运行。这就是有名的链协程。
你也能够经过异步事件循环来调度调用常规函数。咱们看的第一个方法是call_soon。方法call_soon基本上就是尽量的调用你的回调或者事件句柄。它的工做机制相似于先进先出队列,因此若是一些回调须要一段时间来处理任务,其它的回调就会相应的延迟,直到先前的回调结束。让咱们来看一个示例。
import asyncio import functools def event_handler(loop,stop = False): print("Event handler called") if stop: print("Stopping the loop") loop.stop() if __name__ == "__main__": loop = asyncio.get_event_loop() try: loop.call_soon(functools.partial(event_handler,loop)) print("Starting event loop") loop.call_soon(functools.partial(event_handler,loop,stop = True)) loop.run_forever() finally: print("closing event loop") loop.close()
因为asyncio的函数不接受关键字,可是若是咱们须要将关键字传入事件句柄中,那么咱们就须要使用functools模块了。不管什么时候被调用,咱们定义的常规函数将会在标准输出上打印一些文字信息。若是你偶然将这个函数的stop变量设置为True,它将会中止事件循环。
第一次咱们调用它时,咱们没有中止事件循环。第二次咱们调用它时,咱们中止了事件循环。咱们中止事件循环的缘由是咱们将它放入run_forever,这个将时间循环设置为无限循环。一旦循环中止,咱们就能够将它关闭。若是你运行这段代码,你获得的输出以下所示,
Starting event loop Event handler called Event handler called Stopping the loop closing event loop
还有一个相关的函数是call_soon_threadsafe,顾名思义,它与call_soon的工做机制类似,可是它是线程安全的。
若是你想延迟一段时间再调用,你可使用call_later函数。在这个示例中,咱们能够将call_soon函数按照以下方式修改,
loop.call_later(1,event_handler,loop)
这个将会延迟调用咱们的事件句柄1秒钟,而后才会去调用它,并将循环做为第一个参数传入。
若是你想在将来一个指定的时间调度,你须要获取循环的时间,而不是计算机的时间,你能够按照以下方式操做,
current_time = loop.time()
一旦你这样作,你可使用call_at函数,而后将你想调用事件句柄的时间传递给它。让咱们来看看咱们想在5分钟以后调用咱们的事件句柄,下面就是你如何操做的,
loop.call_at(current_time + 300,event_handler,loop)
在这个示例中,咱们使用咱们获取的当前时间,而后加上300秒钟或者5分钟。经过这个操做,咱们延迟调用事件循环5分钟。
Task是Future的一个子类,也是协程的一个框架。Task可让你记录到任务结束处理的时间。因为任务是Future类型,其它的协程能够等待一个任务,当任务处理完毕时你也能够获取到它的结果。让咱们看一个简单的示例。
import asyncio import time async def my_task(seconds): print("This task is take {} seconds to cpmplete".format(seconds)) time.sleep(seconds) return "task finished" if __name__ == "__main__": my_event_loop = asyncio.get_event_loop() try: print("task creation started") task_obj = my_event_loop.create_task(my_task(seconds = 2)) my_event_loop.run_until_complete(task_obj) finally: my_event_loop.close() print("The task's result was :{}".format(task_obj.result()))
在这里,咱们建立一个异步函数,它接受秒数,也是它将会运行的时间。这个模仿了一个长时间运行的任务。而后咱们建立了咱们的事件循环,而且经过事件循环对象的create_task函数建立了一个任务对象。函数create_task接受咱们想要转换为任务的函数。而后咱们运行事件循环,直到任务完成。在最后,一旦任务结束,咱们就得到任务的结果。
经过任务的cancel方法,任务也能够很容易被取消。当你想结束一个任务,调用它就能够了。当一个任务在等待另外一个操做时被取消,这个任务将会报出CancelError错误。
到这里,你应该已经了解如何利用asyncio库进行工做了。asyncio库是很是强大的,它容许你去作不少酷而且有意思的任务。你能够查看http://asyncio.org/,该网站包含了不少使用asyncio的项目,能够获取到不少关于如何使用asyncio库的灵感。固然,Python官方文档也是一个很好的开始asyncio之旅的地方。