Since it began to write blog, write notes before all yourself, so there may be expressed in clear, too winded Hello variety of problems, etc., have any questions or criticisms are welcome in the comments area.
If you are new coroutine, please read the article on acquaintance asyncio coroutine to asyncio have a preliminary understanding.
First, the task object (task assignment)
A reference to the acquaintance asyncio coroutine understand that the task object is an encapsulation of coroutine, which comprises various states, such as blocking state (Suspended), the operating state (running), completion status (DONE);
1. Create a task object in three ways
The first:
loop.create_task(xxx)
The second:
asyncio.ensure_future(xxx)
Third:
asyncio.create_task(xxx)
In fact, the first method is used inside third method, however, it is noted that this method requires a cycle of events that have been run before creating the task, or will throw
RuntimeError:no running event loop
2, add callback
import asyncio
async def get_url(url):
print('start get_url')
await asyncio.sleep(2)
print('end get_url')
return 'Joshua'
def callback(future): # 回调函数
print('Hello {}'.format(future.result()))
if __name__=="__main__":
loop = asyncio.get_event_loop()
task = loop.create(get_url('https://www.baidu.com'))
task.add_done_callback(callback) # 添加回调
loop.run_until_complete(task)
3, pass parameters to the callback function
from functools import partial
# partial(偏函数)可以把函数包装成另外一个函数
import asyncio
async def get_url(url):
print('start get_url')
await asyncio.sleep(2)
print('end get_url')
return 'Joshua'
def callback(url,future):
# 注意:要想向回调函数传递参数需要将参数放在future前面
print('Hello {}'.format(future.result()))
if __name__=="__main__":
loop = asyncio.get_event_loop()
task = loop.create(get_url('https://www.baidu.com'))
# 在传递前需要先用partial将函数封装成另外一个函数
task.add_done_callback(partical(callback,'https://www.baidu.com'))
4, task cancel ()
To request cancellation Task object, which will then be thrown in the next round of the event loop CanceledError
;
Request cancellation:
task.cancel()
Determine whether to cancel:
cancelled()
Determine whether to end: `
done
()`For example, the following
import asyncio async def a(): print('执行a()') await asyncio.sleep(3) print('执行a()完成') async def b(): print('执行b()') await asyncio.sleep(2) print('执行b()完成') if __name__ == '__main__': loop = asyncio.get_event_loop() task1 = loop.create_task(a()) task2 = loop.create_task(b()) task1.cancel() # 取消task1 loop.run_until_complete(asyncio.gather(task1, task2)) # 运行结果如下 >>> 执行b() >>> Traceback (most recent call last): ... # 省略Traceback中的部分 >>> concurrent.futures._base.CancelledError # 如果不想因为抛出异常而中断运行,可以在`gather`中设置`return_exception=True` # 如果设置`return_exception=True`,这样异常会和成功的结果一样处理,并聚合至结果列表,那么运行结果将如下: >>> 执行b() >>> 执行b()完成
We can see
a()
not execute.
5, shield shield canceled
asyncio.shield
( AW , **, Loop = None ), wait for protecting an object is canceled
- aw: Objects can wait, if coroutine will be internally packaged as Task
We canceled the first example above, amend as follows:
import asyncio
async def a():
print('执行a()')
await asyncio.sleep(3)
print('执行a()完成')
async def b():
print('执行b()')
await asyncio.sleep(2)
print('执行b()完成')
def c():
loop = asyncio.get_event_loop()
task1 = loop.create_task(a())
task1 = asyncio.shield(task1) # 设置屏蔽取消操作
task2 = loop.create_task(b())
task1.cancel()
loop.run_until_complete(asyncio.gather(task1, task2, return_exceptions=True))
c()
# 运行结果如下
>>> 执行a()
>>> 执行b()
>>> 执行b()完成
See here still was canceled, little friends are certainly a (sand) face (carved) ignorant (Bo) force (Main), but it really do not blame me ah, it really is output, then why is it so ? We look at shield()
the source code, as follows:
def shield(arg, *, loop=None):
inner = ensure_future(arg, loop=loop) # 首先创建一个内部的Task 赋给inner
if inner.done(): # 如果内部的Task执行完毕则返回inner
# Shortcut.
return inner
loop = futures._get_loop(inner)
outer = loop.create_future() # 在事件循环上创建一个外部的Task
def _done_callback(inner): # 回调函数,在inner执行完毕后回调
if outer.cancelled(): # 如果外部Task被取消
if not inner.cancelled(): # 如果内部Task没被取消
# Mark inner's result as retrieved.
inner.exception() # 根据注释的意思,内部Task的结果会被标记为已检索
return
if inner.cancelled(): # 如果内部的Task取消了,则外部的Task也将取消
outer.cancel()
else:
exc = inner.exception() # 返回内部Task的异常
if exc is not None: # 如果存在异常,则外部Task的异常被设置为内部Task的异常
outer.set_exception(exc)
else: # 否则将外部Task结果设置为内部Task的结果
outer.set_result(inner.result())
inner.add_done_callback(_done_callback)
return outer # 返回外部Task
Can be seen from the above source, running a real internal Task (inner), all external Task on the event loop comes from internal Task, and we canceled it is external Task on the event loop, not on the inside of the Task any impact, so when all Task execution is complete on the event loop exits, while the inside is still running the Task, which led to cancellation although we set up a shield or was canceled, however, that how to solve it?
The following two methods:
Let canceled Task Task consuming consuming less than normal, which is to be canceled
a()
coroutine simulate blocking delay setting thanb()
small coroutine, so the masked canceled coroutine internal Task other than event loop normal Task execution is completed early; as follows:async def a(): print('执行a()') await asyncio.sleep(1) # 由原来的3改为1,使其小于b()的延时 print('执行a()完成') async def b(): print('执行b()') await asyncio.sleep(2) print('执行b()完成')
Execution results are as follows:
>>> 执行a() >>> 执行b() >>> 执行a()完成 >>> 执行b()完成
This can be seen under the
a()
coroutine execution completed.Let the event loop run continuously, it will not be on the Task event loop execution is completed, the program exits leading to internal Task masked canceled coroutine failed to complete the implementation; as follows:
import asyncio import time async def a(): print('执行a()') await asyncio.sleep(3) print('执行a()完成') async def b(): print('执行b()') await asyncio.sleep(2) print('执行b()完成') if __name__ == '__main__': loop = asyncio.get_event_loop() task1 = loop.create_task(a()) task2 = loop.create_task(b()) loop.run_forever() # 输出如下 >>> 执行a() >>> 执行b() >>> 执行b()完成 >>> 执行a()完成
6, other methods of task objects (pasted from official documents)
result
()Return results of the Task. If the Task object has been completed , the result of which the packet will be returned coroutine (or when the coroutine throws an exception, the exception will be re-initiated.) If the Task object is canceled , this method raises an
CancelledError
exception. If the result of the Task object is not available, this method raises anInvalidStateError
exception.exception
()Return exceptions Task object. If the packet coroutine raises an exception, the exception will be returned. If the packet coroutine returns normally the method will return
None
. If the Task object is canceled , this method raises anCancelledError
exception. If the Task object has not been completed , this method throws anInvalidStateError
exception.remove_done_callback
(callback)Removed from the list of callback callback specified callback. This method should only be used in low-level callback-based code. For more details, please see the
Future.remove_done_callback()
documents.get_stack
(**, limit=None*)This stack frame returns a list of the Task object. If the packet is not completed coroutine, where it will return to its pending stack. If the coroutine has been successfully completed or canceled, it returns an empty list. If a coroutine is abnormally terminated, it returns back frame list. From the press frame is always from the old to the new order. Each suspended coroutine returns only one stack frame. The optional limit parameter specifies the maximum number of return frame; default return all frames. Returns a list of the order depends on a return or a stack traceback: returns the latest stack frame, back to return the oldest frame. (This is consistent with the behavior traceback module.)
print_stack
(**, limit=None, file=None*)Print this Task stack object or backtracking. This method produces an output similar to the traceback module
get_stack()
frame acquired. limit arguments are passed directly toget_stack()
. file parameter is the output of the written I / O stream; default output is writtensys.stderr
.classmethod
all_tasks
(loop=None)Returns a collection of all tasks of an event loop. By default, it returns the current event loop in all tasks. If the loop is
None
, it will be usedget_event_loop()
to obtain the current event loop function. Method, IS the this deprecated and removed by Will BE in Python 3.9. The Use at Theasyncio.all_tasks()
function INSTEAD.classmethod
current_task
(loop=None)Returns the current task or run
None
. If the loop isNone
, it will be usedget_event_loop()
to obtain the current event loop function.
Second, the principle invoked coroutine
To better understand the concurrent execution of the back, so now we have to understand the principles under coroutine call, first give coroutine nested example:
import asyncio
import time
async def compute(x,y):
await asyncio.sleep(1)
return x+y
async def print_sum(x,y):
reuslt = await compute(x,y)
print(result)
if __name__ == "__main__":
start = time.time()
loop = asyncio.get_event_loop()
loop.run_until_complete(print_sum(1,2))
loop.close()
print('Cost:{}'.format(time.time()-start))
#输出如下
>>> 3
>>> Cost:1.0007200241088867
Below is a timing chart of this example:
Create an
Event Loop
event loop, callrun_until_complete
start event loop and toprint_sum
create a task object Task,Event Loop
enter the running state, Task enters the pending state;Task driven transport
print_sum
operation toawait compute(x,y)
shift call sub coroutinecompute
,print_sum
entersuspended state, the sub-coroutine
compute
enters the running state;compute
Run toawait asyncio.sleep(1)
the occurrence of blocking time-consuming, enter the suspended state, this time to communicate directly with the Task, and open up a Task channel (sub coroutine with the caller Task), can be roughly understood as similar to the sub-thread communication and process;The advantage of this is to ensure that
print_sum
is carried out simultaneously, and the overall program is run asynchronously, followed by examples to explain the problem.Task communication with the child after turning coroutine and
Event Loop
communication,Event Loop
there is no other Task will only wait for the blocking operation is completed (sleep 1 second);After waiting for time-consuming operation is complete blocking,
Event Loop
by Task coroutine subcompute
channel wakeupcompute
, thencompute
enters the running state;compute
Code next stepreturn x+y
, done into the running state from the state, while thrownStopIteration
exception and carryreturn
results;print_sum
Capturecompute
thrown exception, and acquires from whichcompute
the results returned, this timeprint_sum
from the suspended state to the running state, the next step of the codeprint(result)
In
print_sum
executing theprint
post-converted done by the state running state, and throwsStopIteration
an exception;Task captured
print_sum
after thrown into an abnormal state runing done by the state;In this case
Event Loop
enters stopped state;
For example explains Article 3:
import asyncio
import time
async def compute(x,y):
await asyncio.sleep(1)
return x+y
async def print_sum(x,y):
result = await compute(x,y)
print(result)
if __name__ == "__main__":
start = time.time()
loop = asyncio.get_event_loop()
tasks = [print_sum(1,2),print_sum(1,2)] # 创建两个协程对象
loop.run_until_complete(asyncio.gather(*tasks)) # 同时执行两个协程对象
loop.close()
print('Cost:{}'.format(time.time()-start))
#输出如下
>>>3
>>>3
>>>Cost:1.0004866123199463
As can be seen from the above examples is not performed when the uptime coroutine plurality of objects, in fact, asyncio.gather
part of the action is to turn the object coroutine Task and register them to the event loop . In this example, steps 3 and 4 above explanation timing chart is not blocked waiting, call another but turned print_sum
the Task, is an asynchronous operation , so even if this method is theoretically 1000 coroutine execution objects still only Processed 1 second.
Let's look at an example:
import asyncio
import time
async def compute(x,y):
print('执行compute')
await asyncio.sleep(1)
return x+y
async def print_sum(x,y):
result1 = await compute(x, y) # 调用子协程
print(result1)
result2 = await compute(x, y) # 调用子协程
print(result2)
if __name__ == "__main__":
start = time.time()
loop = asyncio.get_event_loop()
loop.run_until_complete(print_sum(1,2)) # 同时执行两个协程对象
loop.close()
print('Cost:{}'.format(time.time()-start))
# 输出如下
>>> 执行compute
>>> 3
>>> 执行compute
>>> 3
>>> Cost:2.0158169269561768
In this case you can see the overall run time of 2 seconds, verify that print_sum
the synchronization is running, so the implementation of the two sub-coroutine doubling time consuming, if you want to complete it is still one second how to modify?
Nature is a synchronous operation into asynchronous operation, that is, let coroutine objects into a task object (Task), and the following:
import asyncio
import time
async def compute(x,y):
print('执行compute')
await asyncio.sleep(1)
return x+y
async def print_sum(x,y):
task1 = asyncio.create_task(compute(x, y))
task2 = asyncio.create_task(compute(x, y))
await task1
print(task1.result())
await task2
print(task2.result())
if __name__ == "__main__":
start = time.time()
loop = asyncio.get_event_loop()
loop.run_until_complete(print_sum(1,2))
loop.close()
print('Cost:{}'.format(time.time()-start))
# 输出如下
>>> 执行compute
>>> 执行compute
>>> 3
>>> 3
>>> Cost:1.0004889965057373
In this there is a pit, we pay attention to avoid in writing, as follows:
async def print_sum(x,y):
await asyncio.create_task(compute(x, y))
await asyncio.create_task(compute(x, y))
# 这样写依然是同步运行,要将生成的对象赋给一个变量再进行await,即上个例子那样
Three, asyncio concurrent
In several instances calling coroutine above principle, we actually have access to the knowledge portion of the concurrent, let's explain in detail asyncio concurrency.
asyncio complicated by several ways:
asyncio.gather(*aws, loop=None, return_exceptions=False)`
aws
: You can wait for the object (coroutine, Task, Future), if an object is a coroutine can wait, then theasynico.gather
interior will automatically convert it to a Task and add event loop. If all objects wait successfully completed, the result will be a list of all the return values from the same polymerization, and aws sequential order.return_exceptions
When it is the default valueFalse
when aws if the object is canceled can wait, then throws aCancelledError
program run ends; if it is setTrue
when the exception is thrown, the normal operation of the program, will return results in a list this result may object to waitconcurrent.futures._base.CancelledError()
asyncio.wait((aws, *, loop=None, timeout=None, return_when=ALL_COMPLETED)
aws: Objects can wait, run concurrently aws may wait a specified target and block the thread until meeting return_when specified conditions;
loop: This parameter can not ignore it, it will be described according to the official document
python3.10
to be removed in;timeout: supports fractional and integer, if you set will be applied to the maximum number of seconds to wait before returning control, and will not lead
TimeoutError
, if timeout occurs Unfinished Task or Future returns;return_when: Returns the conditions specified timing function:
FIRST_COMPLETED
: Returns a wait completion of the first objects;FIRST_EXCEPTION
: The first occurrence of an exception return, if no abnormality is returned during waiting for all objects is completed;ALL_COMPLETED
: By default, all waiting to return when the object is completed
asyncio.gather
And asyncio.wait
the difference between:
Both are used for concurrent coroutine, which
asyncio.gather
returns operation results coroutineasyncio.wait
returns two Task / Future list (coroutine and coroutine two lists is not completed is completed);asyncio.gather
Equivalent to the entire black box, just to tell you the results of coroutines;asyncio.gather
Task group may be, for example:import asyncio async def get_url(url): print('start get_url') await asyncio.sleep(2) print('end get_url') return 'Joshua' if __name__=="__main__": loop = asyncio.get_event_loop() # 将url分组 group1 = [get_url('https://www.baidu.com') for _ in range(10)] group2= [get_url('https://www.baidu.com') for _ in range(10)] group1.cancel() # 将第一组任务取消 loop.run_until_complete(asyncio.gather(*group1,*group2))
asyncio.gather
Will return and complete implementation of the unfinished task, you can do some operations in its operation, such as the abovereturn_when
returns the actual conditions to control when to return the results if you want to perform their own needs with the result from the completion of the task which method take;Get example returns the results as follows:
tasks = [Task1,Task2,Task3] # gather的获取结果方法 results = await asyncio.gather(*tasks) # 注意此处gather方法不支持可迭代对象做参数,需要加*脱掉[] # wait的获取结果方法 # 第一种 done,pending = await asyncio.wait(tasks) # 标准格式的wait方法 results = [task.result() for task in done] # 第二种 await asyncio.wait(tasks) results = [task.result() for task in tasks]