- 事件循环基类
- 事件循环基类
事件循环是由asyncio提供的核心执行装置。它提供了多种服务,包括:
注册、执行和关闭延时调用(超时)
为各种通信创建客户端和服务端传输
为一个外部程序通信启动子进程和相关的传输
把高成本的函数调用委托到线程池
class asyncio.BaseEventLoop
此类是一个实现细节。此类是AbstractEventLoop的子类,是你在asyncio中找到的具体事件循环类的基类。它不应该被直接使用,而是使用AbstractEventLoop来代替它。BaseEventLoop不应该被第三方代码继承,它的内部接口并不稳定。
class asyncio.AbstractEventLoop
事件循环抽象基类。
这个类不是线程安全的。
1.1. 运行事件循环
AbstractEventLoop.run_forever()
一直运行直到stop()被调用。如果stop()在run_forever()之前被调用,它将轮询一次I/O selector(timeout为0),然后运行所有响应I/O事件的回调(以及那些已经被安排的回调),然后退出。如果stop()在run_forever()运行时被调用,它将运行当前的一批回调然后退出。请注意,那些被回调函数安排的回调不会在这种情况下运行;它们将会在下一次run_forever被调用时运行。
改动于3.5.1版本。
AbstractEventLoop.run_until_complete(future)
一直运行直到future运行完成。
如果参数是一个协程对象,它将被ensure_future()打包。
返回Future的结果,或者引发它的异常。
AbstractEventLoop.stop()
停止运行事件循环。
它将在一个适当的时机使得run_forever()退出。
改动于3.5.1版本。
AbstractEventLoop.is_closed()
当事件循环关闭时返回True。
新增于3.4.2版本。
AbstractEventLoop.close()
关闭事件循环。这个事件循环必须没有在运行。挂起的回调将会丢失。
此方法清空队列、关闭执行器,且不会等待执行器运行结束。
此方法是幂等且不可逆的。在此方法调用之后不应该调用任何其他方法。
1.2. 调用
大多数asyncio函数不接受参数。如果你想给你的回调函数一些参数,使用functools.partial()。例如,loop.call_soon(functools.partial(print, "Hello", flush=True))将会调用print("Hello", flush=True)。
注意:functools.partial()比lambda方法更好,因为asyncio能检查functools.partial()对象,使得能在调试模式下展示参数。相比之下使用lambda会有糟糕的表现。
AbstractEventLoop.call_soon(callback, *args)
安排尽快调用这个callback(回调)。这个callback会在call_soon()返回、当控制权返回事件循环时被调用。
这个操作是一个先入先出队列,多个call_soon()注册了多个callback会按照它们的顺序依次被调用。每个callback都只会被调用一次。
callback后面的所有参数会在callback被调用时传递给callback。
返回值是asyncio.Handle的一个实例,用于中止这个callback。
(可以使用functools.partial()来给callback传递参数)
AbstractEventLoop.call_soon_threadsafe(callback, *args)
类似call_soon(),但是是线程安全的。
可以参考使用asyncio开发的 并发和多线程 段落。
1.3. 延时调用
事件循环有自己的内部时钟用于计算超时。这个时钟依赖于(特定平台的)事件循环来执行;理想情况下它是一个单调时钟(monotonic clock)。通常它是一个不同于time.time()的时钟。
注意:超时(相对延时或绝对时刻)不应该超过一天。
AbstractEventLoop.call_later(delay, callback, *args)
安排一个callback在delay秒之后被调用(delay可以是int和float)。
返回值是asyncio.Handle的一个实例,用于中止这个callback。
每次调用call_later()都有callback将被调用一次。如果多个回调被安排在同一时刻调用,谁先被调用是不确定的。
可选参数args将被传递给callback作为调用时的参数。如果你想给callback传递带名字的参数,把它们包起来或者用functools.partial()。
AbstractEventLoop.call_at(when, callback, *args)
安排callback在给定的时间戳when(int或者float)时调用;用AbstractEventLoop.time()作为参考。
这个方法的行为和AbstractEventLoop.call_later()一样。
返回值是asyncio.Handle的一个实例,用于中止这个callback。
AbstractEventLoop.time()
返回当前时间,一个float值,根据事件循环的内部时钟。
1.4. Futures
AbstractEventLoop.create_future()
创建一个关联到这个事件循环的asyncio.Future对象。
这是asyncio中创建Future的首选方式,作为事件循环的实现可以提供Future类的代替实现(有更好的性能或表现)。
新增于3.5.2版本。
1.5. 任务
AbstractEventLoop.create_task(coro)
安排一个协程对象的执行:把它包装在一个future里。返回一个任务(Task)对象。
第三方事件循环可以使用他们自己的Task子类来交互。在这种情况下,返回值类型是Task的子类。
这个方法是Python 3.4.2版本加入的。使用async()可以支持旧的Python版本。
新增于3.4.2版本。
AbstractEventLoop.set_task_factory(factory)
为AbstractEventLoop.create_task()设置一个task的工厂。
如果factory参数是None,将会设置为默认工厂。
如果factory参数是可被调用(callable)的,他应该可以接受(loop, coro)作为参数,loop是一个可用的事件循环,coro是协程对象。它必须返回一个asyncio.Future的兼容对象。
新增于3.4.4版本。
AbstractEventLoop.get_task_factory()
返回任务工厂,如果用的是默认的工厂,返回None。
新增于3.4.4版本。
1.6. 创建连接
coroutine AbstractEventLoop.create_connection(protocol_factory, host=None, port=None, *, ssl=None, family=0, proto=0, flags=0, sock=None, local_addr=None, server_hostname=None)
创建一个连接到给定的host(主机)和port(端口)的流传输连接:socket family是AF_INET还是AF_INET6取决于host(或者指定了family参数),socket type是SOCK_STREAM。protocol_factory必须是可被调用(callable)的且返回一个传输(protocol)实例。
这是一个协程方法,它将在后台默默地建立一个连接。建立成功的时候,返回一个(transport, protocol)元组((传输,协议)对)。
按时间顺序的大概底层操作如下:
连接建立,创建一个传输(transport)来代表它。
protocol_factory被调用(没有参数),返回一个协议(protocol)实例。
这个协议(protocol)实例和传输(transport)捆绑在一起,其connection_made()方法被调用。
这个协程成功地返回(transport, protocol)对。
创建的传输是一个依赖于实现的双向流。
注意:protocol_factory可以是任意的可被调用(callable)对象,不一定要是一个类。例如,你想使用一个预先创建的协议实例,你可以写lambda: my_protocol。
这些选项可以改变连接创建方式:
ssl:如果给了这个参数且不为false,将会创建一个SSL/TLS传输(默认是一个普通的TCP传输被创建)。如果ssl是一个ssl.SSLContext对象,这个上下文将被用于创建传输;如果ssl是True,一个带有一些未被指明的默认设置的上下文将被使用。(可以去参考官方文档ssl章节的SSL/TLS security considerations段落)
server_hostname,只能和ssl参数一起使用,用于设置或覆盖将要匹配目标主机证书的主机名。默认会使用host参数。如果host参数为空,没有默认值,你必须填写server_hostname参数的值。如果server_hostname是一个空字符串,主机名匹配就被禁用了(这非常不安全,可能会产生中间人攻击)。
family,proto,flags是可选的地址族(address family)、协议(protocol)、位掩码(flags),来传给getaddrinfo()进行host参数的域名解析。如果给定这些参数,这些应该都是相应的socket模块的常量整数。
sock,如果给定这个参数,它必须是已存在的、已连接的socket.socket对象,来用于传输。如果给定了这个参数,host、port、family、proto、flags和local_addr参数都不应该给定。
local_addr,如果给定了这个参数,它应该是一个来用绑定到本地socket的(local_host, local_port)元组((本地地址,本地端口)元组)。local_host和local_port将使用getaddrinfo()来查询,类似于host和port参数。
改动于3.5版本:SSL/TLS现在支持Windows上的ProactorEventLoop了。
还可以看看open_connection()函数,它可以获得(StreamReader, StreamWriter)对,而不是一个协议。
coroutine AbstractEventLoop.create_datagram_endpoint(protocol_factory, local_addr=None, remote_addr=None, *, family=0, proto=0, flags=0, reuse_address=None, reuse_port=None, allow_broadcast=None, sock=None)
创建一个数据报连接:socket family是AF_INET还是AF_INET6取决于host(或者指定了family参数),socket type是SOCK_DGRAM。protocol_factory必须是可被调用(callable)的且返回一个传输(protocol)实例。
这是一个协程方法,它将在后台默默地建立一个连接。建立成功的时候,返回一个(transport, protocol)元组((传输,协议)对)。
这些选项可以改变连接创建方式:
local_addr,如果给定了这个参数,它应该是一个来用绑定到本地socket的(local_host, local_port)元组((本地地址,本地端口)元组)。local_host和local_port将使用getaddrinfo()来查询。
remote_addr,如果给定了这个参数,它应该是一个来用绑定到远程地址的socket的(remote_host, remote_port)元组((本地地址,本地端口)元组)。remote_host和remote_port将使用getaddrinfo()来查询。
family,proto,flags是可选的地址族(address family)、协议(protocol)、位掩码(flags),来传给getaddrinfo()进行host参数的域名解析。如果给定这些参数,这些应该都是相应的socket模块的常量整数。
reuse_address,这个参数告诉内核在TIME_WAIT状态下重用本地的socket,而不是等待它自然的超时过期。如果没有指定这个参数,在UNIX下默认设为True。
reuse_port,这个参数告诉内核允许这个端点绑定到这个端口,即使有另外的已存在的端点绑定了这个端口,只要它们在创建时也设置了这个参数就行。这个选项在Windows和部分UNIX上不被支持。如果SO_REUSEPORT这个常量没有被定义,那这个能力就是不支持的。
allow_broadcast,这个参数告诉内核允许这个端点发送消息给广播地址。
sock参数可以被指定来使用一个先前存在的、已连接的socket.socket对象来用于传输。如果指定了这个参数,local_addr和remote_addr必须被省略(必须为None)。
在Windows上的ProactorEventLoop循环里,这个方法还不被支持。
可以参考UDP客户端协议和UDP服务端协议实例。
coroutine AbstractEventLoop.create_unix_connection(protocol_factory, path, *, ssl=None, sock=None, server_hostname=None)
创建一个UNIX连接:socket family是AF_UNIX,socket type是SOCK_STREAM。AF_UNIX是用来进行同一机器上的不同进程间的高效通信的。
这是一个协程方法,它将在后台默默地建立一个连接。建立成功的时候,返回一个(transport, protocol)元组((传输,协议)对)。
参数可以参考AbstractEventLoop.create_connection()方法的参数。
仅适用于:UNIX。
1.7. 创建监听连接
coroutine AbstractEventLoop.create_server(protocol_factory, host=None, port=None, *, family=socket.AF_UNSPEC, flags=socket.AI_PASSIVE, sock=None, backlog=100, ssl=None, reuse_address=None, reuse_port=None)
创建一个TCP服务端(socket type是SOCK_STREAM),绑定到host和port参数(主机和端口)。
返回一个Server对象,它的sockets属性包含了已创建的socket。使用Server.close()方法来停止这个服务端:关闭监听的sockets。
参数:
host参数可以是一个字符串,在这种情况下TCP服务端绑定到host和port参数。host参数也可以是字符串序列,这种情况下TCP服务端绑定到host序列里的所有主机。如果host是空字符串或者None,所有的接口都被假定,返回一个有多个socket的list(最可能的用于IPv4的一个和IPv6的一个)。
family参数可以设置为socket.AF_INET或者AF_INET6来使socket强制使用IPv4或者IPv6。如果没有设置就会取决于host(默认是socket.AF_UNSPEC)。
flags是用于的getaddrinfo()的位掩码。
sock参数可以被指定,用来使用先前存在的socket对象。如果指定了,host和port参数应该被省略(必须为None)。
backlog参数是传递给listen()方法的最大等待连接数(默认为100)。
ssl参数可以设置一个SSLContext对象来在一个接受的连接上启用SSL。
reuse_address,这个参数告诉内核在TIME_WAIT状态下重用本地的socket,而不是等待它自然的超时过期。如果没有指定这个参数,在UNIX下默认设为True。
reuse_port,这个参数告诉内核允许这个端点绑定到这个端口,即使有另外的已存在的端点绑定了这个端口,只要它们在创建时也设置了这个参数就行。这个选项在Windows上不被支持。
这个是一个协程方法。
改动于3.5版本:SSL/TLS现在支持Windows上的ProactorEventLoop了。
还可以看看start_server()函数,它创建一个(StreamReader, StreamWriter)对,并且回调一个方法。
改动于3.5.1版本:host参数现在可以是一个字符串序列。
coroutine AbstractEventLoop.create_unix_server(protocol_factory, path=None, *, sock=None, backlog=100, ssl=None)
类似于AbstractEventLoop.create_server(),但是具体的socket family是AF_UNIX。
这个是一个协程方法。
仅适用于:UNIX。
1.8. 监视文件描述符(file descriptor)
对于Windows上的SelectorEventLoop,只有socket处理被支持(例如:管道文件描述符不被支持)。
对于Windows上的ProactorEventLoop,这些方法都不被支持。
AbstractEventLoop.add_reader(fd, callback, *args)
开始监视可读的文件描述符fd,然后会传递给定的args来调用callback。
(可以使用functools.partial()来给callback传递参数)
AbstractEventLoop.remove_reader(fd)
停止监视这个可读的文件描述符。
AbstractEventLoop.add_writer(fd, callback, *args)
开始监视可写的文件描述符fd,然后会传递给定的args来调用callback。
(可以使用functools.partial()来给callback传递参数)
AbstractEventLoop.remove_writer(fd)
停止监视这个可写的文件描述符。
最下面有这些方法的实例。
1.9. 低级socket操作
coroutine AbstractEventLoop.sock_recv(sock, nbytes)
从socket获取数据。仿照的socket.socket.recv()这个阻塞方法。
返回值是bytes(字节)对象,代表接受的数据。一次接受的字节数的最大值由nbytes参数指定。
SelectorEventLoop事件循环调用这个方法的时候,sock参数必须是非阻塞的socket。
这个是一个协程方法。
coroutine AbstractEventLoop.sock_sendall(sock, data)
给socket发送数据。仿照的socket.socket.sendall()这个阻塞方法。
这个socket必须连接一个远程的socket。这个方法持续的发送来自于data的数据,直到全部发送完毕或者有错误产生。成功时返回None。错误时会引发一个异常,并且此时没有办法确定多少数据(如果有的话)被成功地由该连接的接收端处理。
SelectorEventLoop事件循环调用这个方法的时候,sock参数必须是非阻塞的socket。
这个是一个协程方法。
coroutine AbstractEventLoop.sock_connect(sock, address)
连接到地址为address的远程socket上。仿照的socket.socket.connect()这个阻塞方法。
SelectorEventLoop事件循环调用这个方法的时候,sock参数必须是非阻塞的socket。
这个是一个协程方法。
改动于3.5.2版本:address参数不一定需要被解析。socket_connect会通过调用socket.inet_pton()来检查address是否被解析了。如果没有,AbstractEventLoop.getaddrinfo()将被用于解析这个address。
coroutine AbstractEventLoop.sock_accept(sock)
接受一个连接。仿照的socket.socket.accept()这个阻塞方法。
sock这个参数必须是已绑定到一个地址并且监听连接的socket。返回值是(conn, address)对,conn是一个新socket对象用于在连接上发送和接受数据,address是绑定到socket上的连接的另一端的地址。
sock参数必须是非阻塞的socket。
这个是一个协程方法。
1.10. 解析主机名
coroutine AbstractEventLoop.getaddrinfo(host, port, *, family=0, type=0, proto=0, flags=0)
这是一个协程方法,类似于socket.getaddrinfo(),但是不阻塞。
coroutine AbstractEventLoop.getnameinfo(sockaddr, flags=0)
这是一个协程方法,类似于socket.getnameinfo(),但是不阻塞。
1.11. 管道连接
Windows上的SelectorEventLoop不支持这些方法。Windows上的ProactorEventLoop支持这些方法。
coroutine AbstractEventLoop.connect_read_pipe(protocol_factory, pipe)
在事件循环里注册 读管道。
protocol_factory参数应该是带有Protocol接口的实例对象。pipe参数应该是一个类似文件(file-like)的对象。返回(transport, protocol)对,transport支持ReadTransport的接口。
SelectorEventLoop事件循环调用这个方法的时候,pipe必须是非阻塞模式。
这个是一个协程方法。
coroutine AbstractEventLoop.connect_write_pipe(protocol_factory, pipe)
在事件循环里注册 写管道。
protocol_factory参数应该是带有BaseProtocol接口的实例对象。pipe参数应该是一个类似文件(file-like)的对象。返回(transport, protocol)对,transport支持WriteTransport的接口。
SelectorEventLoop事件循环调用这个方法的时候,pipe必须是非阻塞模式。
这个是一个协程方法。
可以参考AbstractEventLoop.subprocess_exec()和AbstractEventLoop.subprocess_shell()方法。
1.12. UNIX信号
仅适用于:UNIX。
AbstractEventLoop.add_signal_handler(signum, callback, *args)
为信号添加一个处理程序。
如果信号的数值是无效的或者获取不了,会引发ValueError异常。如果设置处理程序时出现问题,会引发RuntimeError异常。
(可以使用functools.partial()来给callback传递参数)
AbstractEventLoop.remove_signal_handler(sig)
为信号移除处理程序。
如果信号处理程序被移除,返回True,否则返回False。
可以参考官方文档的signal模块。
1.13. 运行器(Executor)
在Executor(线程池或进程池)里调用函数。默认情况下,事件循环使用着一个线程池运行器(ThreadPoolExecutor)。
coroutine AbstractEventLoop.run_in_executor(executor, func, *args)
安排func在指定的运行器(executor)里调用。
executor参数应该是一个Executor实例。如果executor参数为None,就使用默认运行器。
(可以使用functools.partial()来给func传递参数)
这个是一个协程方法。
AbstractEventLoop.set_default_executor(executor)
为run_in_executor()函数设置默认运行器。
1.14. 错误处理API
让你定制事件循环里的异常处理。
AbstractEventLoop.set_exception_handler(handler)
把handler设置为新的事件循环的异常处理程序。
如果handler为None,就会设置为默认的异常处理程序。
如果handler是可被调用(callable)对象,他应该匹配(loop, context)作为参数,其中loop是一个活跃的事件循环,context是一个dict对象(具体参考下面的call_exception_handler()方法里的context)。
AbstractEventLoop.get_exception_handler()
返回异常处理程序,如果使用的是默认的那个就会返回None。
新增于3.5.2版本。
AbstractEventLoop.default_exception_handler(context)
默认的异常处理程序。
这个函数在异常发生且没有设置异常处理程序时被调用;也能被自己定制的异常处理程序调用,如果你想推迟默认行为的话。
context参数和下面的call_exception_handler()里的context意义一样。
AbstractEventLoop.call_exception_handler(context)
调用当前事件循环的异常处理程序。
context是一个包含以下关键字作为key的dict(一些新的key可能会在后面介绍):
'message': 错误消息;
'exception'(可选): 异常对象;
'future'(可选): asyncio.Future实例;
'handle'(可选): asyncio.Handle实例;
'protocol'(可选): Protocol(协议)实例;
'transport'(可选): Transport(传输)实例;
'socket'(可选): socket.socket实例。
注意:这个方法不应该被任何事件循环的子类重载。想使用自己定制的异常处理程序,请使用set_exception_handler()方法。
1.15. 调试模式
AbstractEventLoop.get_debug()
得到事件循环是否调试模式(布尔值)。
如果环境变量PYTHONASYNCIODEBUG被设置为一个非空的字符串,默认值是True;否则是False。
新增于3.4.2版本。
AbstractEventLoop.set_debug(enabled: bool)
为事件循环设置调试模式。
新增于3.4.2版本。
可以参考最后一章 使用asyncio开发 的 asyncio的调试模式 章节。
1.16. 服务端(Server)
class asyncio.Server
监听socket的服务端。
通过AbstractEventLoop.create_server()方法和start_server()方法来创建对象;不要直接实例化这个类。
close()
关闭服务:关闭监听的socket并且设置这些socket的属性为None。
那些代表现有的客户端连接的socket就悬空了。
服务端以同步方式关闭;使用wait_close()协程方法会等着服务端被关闭。
coroutinewait_close()
等待close()方法完成。
这是一个协程方法。
sockets
服务端正在监听的socket.socket对象的list;如果已经关闭了,会得到None。
1.17. Handle
class asyncio.Handle
AbstractEventLoop.call_soon()、AbstractEventLoop.call_soon_threadsafe()、AbstractEventLoop.call_later()和AbstractEventLoop.call_at()的返回值,一个回调包装对象。
cancel()
关闭这个回调。如果这个回调已经关闭了或者已经运行了,这个方法无效。
1.18. 事件循环实例
1.18.1. 使用call_soon()的Hello World
使用call_soon()方法来安排一个回调的实例。这个回调函数显示“Hello World”然后停止事件循环。
import asyncio
def hello_world(loop):
print('Hello World')
loop.stop()
loop = asyncio.get_event_loop()
安排调用hello_world()loop.call_soon(hello_world, loop)
阻塞调用由loop.stop()中断loop.run_forever()
loop.close()
也可以查看使用协程的Hello World协程实例。
1.18.2. 使用call_later()显示当前时间
通过回调方式每秒钟显示当前时间的实例。在五秒钟之内,回调使用call_later()方法来安排它自己然后停止事件循环。
import asyncio
import datetime
def display_date(end_time, loop):
print(datetime.datetime.now())
if (loop.time() + 1.0) < end_time:
loop.call_later(1, display_date, end_time, loop)
else:
loop.stop()
loop = asyncio.get_event_loop()
安排第一次调用display_date()end_time = loop.time() + 5.0
loop.call_soon(display_date, end_time, loop)
loop.run_forever()
loop.close()
也可以查看使用协程的显示当前时间协程实例。
1.18.3. 监视文件描述符的读取事件
使用add_reader()方法等待直到文件描述符获取了一些数据,然后停止事件循环。
import asyncio
try:
from socket import socketpair
except ImportError:
from asyncio.windows_utils import socketpair
rsock, wsock = socketpair()
loop = asyncio.get_event_loop()
def reader():
data = rsock.recv(100)
print("Received:", data.decode())
# 我们完成了:注销文件描述符
loop.remove_reader(rsock)
# 停止事件循环
loop.stop()
loop.add_reader(rsock, reader)
模拟来自网络的数据接收loop.call_soon(wsock.send, 'abc'.encode())
运行事件循环loop.run_forever()
我们完成了:关闭socket和事件循环rsock.close()
wsock.close()
loop.close()
1.18.4. 为SIGINT和SIGTERM设置信号处理程序
使用add_signal_handler()方法为SIGINT和SIGTERM信号量注册处理程序。
import asyncio
import functools
import os
import signal
def ask_exit(signame):
print("got signal %s: exit" % signame)
loop.stop()
loop = asyncio.get_event_loop()
for signame in ('SIGINT', 'SIGTERM'):
loop.add_signal_handler(getattr(signal, signame),
functools.partial(ask_exit, signame))
print("Event loop running forever, press Ctrl+C to interrupt.")
print("pid %s: send SIGINT or SIGTERM to exit." % os.getpid())
try:
loop.run_forever()
finally:
loop.close()
这个例子仅仅工作在UNIX上。