异步问题
官方文档:
看了一大堆相关的资料和教程, 针对的版本不同, 写法也各不一致, 翻了翻官方的文档, 发现其实越高版本的对异步进行封装的越方便, 官方说法叫高层级API, 甚至都不知道要去理解什么task或者loop之类的概念了, 我现在用的是3.7.5, 可以这样很简单的实现阻塞等待或者异步并发: 如果没有复杂需求的话, 用高层级API就可以了。
要是需要处理回调这个情况的话, 好像还得动用那些比较底层的功能接口这一块内容呢, 打算在后续的文档里再做一个单独方面的记载和说明。
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
async def first():
print('first函数调用开始,下面将会等待3秒模拟函数运行完毕')
现在请执行等待, 具体来说就是睡眠三个单位的时间。
print('first函数执行完毕')
async def last():
我在这儿把打印这个操作给拿出来做一做, 然后紧接着就是那个名字被叫做最后一个的函数, 它是开始的那个时间点。
await .sleep(2)
print('last函数执行完毕')
async def func(delay):
print('开始异步同时执行的函数+延迟: ' + str(delay))
等待一下, 这个时间是用delay指定的秒数。
先执行异步函数, 然后进行延迟处理, 最后打印出信息, 这里的信息是由字符串‘--异步函数执行完毕+延迟: ’与延迟时间的字符串形式相加得到的。
async def main():
先调用first()函数, 然后等待这个函数执行完毕之后, 才会接着去进行接下来的操作。
await .(
func(1),
func(2),
func(3)
await last()
.run(main())
在实际执行上面这段代码的时候, 整个过程是这样的:
官方文档给出的建议情况, 是希望大家去创建唯一一个用于作为主入口的 main() 函数(这个函数的名称是可以进行自定义调整的), 随后应当把那些需要被调用的其他函数, 全都放置在这个函数内部, 再接着使用 .run() 来进行启动操作, 在理想的状况之下, 上述操作流程只允许被调用一次。
上图:
[05]
更新
上面所说的、属于高阶层API用法的那一行代码中的, 调用run函数并将main函数的运行结果作为实参传给它这个动作, 与下面这段使用了低阶层API、通过另外一种方式来实现最终效果的代码相比, 二者在执行层面是完全一致的。
loop = .()
task = loop.(main())
对(task)进行loop。
以下是学习过程中所记录的, 关于偏低层实现的资料, 这些内容涵盖了最基本的定义以及应用。
10
11
我们这里定义一个函数, 这个函数的作用是能够进行异步调用, 它的具体类型是。
这是一个异步函数叫做func1, 它通过一个def关键字来定义。
pass
if == '':
loop这个对象是通过调用空的括号所定义出来的, 它的主要作用是用来执行异步函数的循环任务。
task = .(func1()) # 创建一个调用异步函数的任务,task类型为
# task = loop.(func1()) # 使用loop的.()创建任务,效果一样
在loop循环当中去执行那个task这一个异步函数, 一直到这个函数最终运行完成为止。
用点号后面紧跟一个括号, 这个方法可以创建出一个任务对象。还有一种写法是写loop这个词, 然后跟着点号, 再跟一个左括号和一个右括号, 这种形式也能创造一个任务。这个方法的参数要求是一个具体的对象存在。
当传入一个一个协程的时候, 其内部会自动地把它封装成为一个task对象, 然后这个task对象是属于那个特定的子类。
(task, .)这个情况是会导致输出结果为True的。
像这样的类型里的任务, 是可以在loop里头去执行的嘛, 当然了, 如果你直接把这个任务的变量名给拿过来用await去把它给阻塞掉了也是完全可以的。
什么时候使用异步
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
这是一个异步的函数, 它所需要花费的时间是非常少的。
这里有一件事情, 它是名叫那个文本内容, 它是以异步这种形式来作为一。
等待这个暂停的过程, 大概也就是零点一秒的时间, 就这么简单地等着。
print(text)
这个函数运行的时间比较长, 而且它属于异步类型。
async def ():
执行那个输出的代码, 内容是空字符串。
现在请执行等待, 具体来说就是睡眠三个单位的时间。
执行那个输出的代码, 内容是空字符串。
主函数这个地方, 也是得把它声明成那个异步类型才行。
async def main():
await msg('first')
目前的时候, 是需求调用一个会花费比较长时间的函数操作, 而心里面并不希望处于那种阻塞的、等待它执行完毕的状态。
希望在开始执行这一操作之后, 紧接着就去调用msg这个方法, 在这个地方, 我们就可以把内容给封装到任务task里面去。
任务等于点括号加三个右括号。
await msg('')
# 开始task中的函数
需要等待这个任务的完成。
task执行完毕了之后, 就接着继续往下写代码, 也就是进行下面的这些操作。
print('All done.')
如果该变量中的内容是空的字符串, 那么。
loop = .()
loop.(main())
运行结果:
[16]
并发和并行
并发和并行这两件事, 一直都是非常容易被人搞混的概念。通常情况下, 咱们所说的并发, 指的是有好多个任务都想要在同一时间里同时进行。但是呢, 并行这个概念, 说的是在同一个确切的时间点上, 确实有多个任务正在实实在在地执行中。
如果用讲课来当例子说明这个情况的话, 那么在并发处理的时候, 就相当于同一位老师在一个特定的时间段内同时兼顾到好几个不同的人, 并且分别给他们辅导功课。
所谓的并行模式, 实际上指的是有好几位老师, 这几位老师各自都在同时发挥作用, 他们分别去辅助对应的那部分学生处理各自的功课事宜。
简单来说, 就是一个人一口气把三个馒头都吃了, 还是三个人每人各吃一个的情况, 这其中的每一个行为, 不管是谁去做的, 只要吃进去一个完整的馒头, 就算作是一个单独的任务来处理。
为了实现并发处理的目的, 必须依赖于多个协程共同合作以完成相应的任务。一旦有某个任务发生了阻塞的情况, 就要使用await等待。与此同时, 其他未受影响的协程会继续执行工作。
我们需要去创建属于多个协程的那个特定的列表这个动作, 然后, 要把这些已经存在了的协官, 一个一个地拿出来, 注册到那个被称为事件循环的东西里头去。
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
time
now = : time.time()
这是一个异步定义的函数, 该函数的形式参数是x。
显示一个冒号, 后面跟着空格, 然后显示变量 x 的值。
await .sleep(x)
'Done after {}s'.(x)
start = now()
= (1)
= (2)
= (4)
tasks =
.(),
.(),
.()
loop = .()
这是一个循环的过程,它会等待着所有任务的完成操作。
对于任务列表里的每一个任务, 我们都要把它逐个地拿出来进行处理。
print('Task ret: ', task.())
输出时间, 这个时间是通过当前时间来减去起始时间的。
结果如下
: 1
: 2
: 4
这个任务已经完成了, 总共花费了大概一秒钟的时间。
任务已经完成, 这一过程花费了两秒钟, 所以结果是已经做完的状态。
Task ret: Done after 4s
当前显示的时刻数值为4.1133。
总用时大概在4秒左右, 这4秒的阻塞时间长度, 完全能够让前面那两个协程把各自的执行任务给全部做完, 而如果按照同步顺序这种老方式去处理那些任务的话, 所需的等待时间加起来至少也得是7秒, 由此可见, 此时此刻我们确实已经实现了并发处理的效果。
对于`.wait(tasks)这个方法`来说`, `它也是可以`够使用`.(*tasks)这个形式的`, `其中前者的那个情况是接受一个task的列表`, `而后者的那种情况则是接收一堆task`。
异步结果回调
找了别人写的例子, 大致理解了实现过程。
10
11
12
13
14
15
16
17
18
19
20
21
22
time
now = : time.time()
这是一个异步定义的函数, 该函数的形式参数是x。
显示一个冒号, 后面跟着空格, 然后显示变量 x 的值。
'Done after {}s'.(x)
def ():
显示冒号和空格, 以及调用某个方法之后的结果字符串。
start = now()
= (2)
loop = .()
把任务设置成调用循环的这个操作。
那个代码片段看起来像是用来创建任务的写法。从目前的情况来看, 这样写可能和前面使用循环来创建任务所产生的效果是一样的。
task.()
对(task)进行loop。
输出时间, 这个时间是通过当前时间来减去起始时间的。
大家必须得搞清楚这么个事儿, 就是当大伙儿拿那些比较低层级的接口去手动搞那个异步任务的时候, 可千万别同时再去用那些高层级接口里头那种简简单单的操作了, 就比方说, 就在这个地方, 要创建那个叫做task的任务对象的时候, 你要是敢去调用那个带点号的玩意儿, 哎哟喂, 那就要出大问题了, 到时候系统啊就会直接告诉你哎呀找不到那个循环对象的。
这个情况是没有事件循环。
翻开了tasks.py源代码看了一下, 原来大家所说的所谓的高层级API, 其实就是这样的简单封装啊……
调用的时候, 在函数内部呢, 又定义了一遍那个叫loop的变量。
def (coro):
在一个生成任务的里面存在的那个东西的所属权。
a Task .
"""
loop = .()
那个东西是循环的样子。
我自己写的例子
开始创建出来四个异步任务, 让它们在同一时刻进入执行状态, 等到每个任务都执行完毕以后, 就把得到的结果依次追加放进那个数组变量里面去。
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
=
这是一个异步函数, 它的名字叫作fun, 参数是var。
var + 1
def ():
.(.())
=
对于每一个i, 其取值范围是从1到4。
cor = fun(i)
这个任务是基于相关的对象来进行的。
task.()