异步的精髓在于并发。并发的核心思想是:在同步执行的基础下,先执行完当前的一个任务,在任务的某一个地方可以等待其自己完成时,去操作另一个任务;直到操作执行到该任务的等待部分,再转到下一个需要执行的任务。
简单来说,其实就是要把所有衣服塞进十台洗衣机,先塞满一台,按下开关,不等这台洗衣机洗完就去开另一台洗衣机,在搞完这台洗衣机之后再去搞其他洗衣机。大大提升了效率。
但是单单只有并发,最大的缺点就是:它是无序混乱的,而且无法灵活的变更数据。但是turingAPI的异步模型为您提供了一个解决办法——数据关联和数据互补
那么,我们下面先开始来学习最基础的并发。
分层并发
分层并发是turingAPI异步模型的一个非常重要的概念。其核心思想是:
封装一层,再封装一层,最后再封装一层。
是的,可能就是这么的离谱。但是这种“多层封装”的思想实际上是非常实用的。
turingAPI异步模型中最基础的单位是 task
,task可以将一个同步函数包装为一个可以进行异步调用的task单位。
举个例子,我们需要使用turingAPI刷浏览量,但是使用while
循环+getWorkDetail
这种方法实在太慢,这时我们就要用到异步。但是问题来了,getWorkDetail
方法是一个同步的方法,它根本是无法被以异步形式调用的。可能有人会说到可以用asyncio.get_running_loop().run_in_executor(None,user.getWorkDetail,.......)
这种方式来进行异步调用,但是这太麻烦。我们可以直接使用turingAPI提供的task类,将其变为一个包含了异步调用方法的对象,并且可以对其进行正常的调用。
task
task类的所有变量成员如下所示:
Copy func=None #要调用的函数
name=None #任务名称(很重要,在数据关联、数据互补时task进行互相辨认的唯一途径)
args=[] #直接传参
kwargs={} #关键字传参,参数名称(str):传参内容(any)
returnFunc=None #在完成调用函数后将返回的值放入所属事件流时,使用的处理函数。传参格式为:func
#的返回值,task对象的self,所以returnFunc储存的函数必须只有两个必填参数
association=None #需要进行关联的task名称
event=None #所属事件流
associationFunc=None #数据关联、数据互补拿到数据后进行对数据处理的函数
__init__方法:
这里不过多赘述,直接上代码。
Copy def __init__(self,func,name=None,args=[],kwargs={},returnFunc=None,event=None,association=None,associationFunc=None):
self.func=func
self.name=name
self.args=args
self.kwargs=kwargs
self.returnFunc=returnFunc
self.event=event
self.association=association
self.associationFunc=associationFunc
run方法:
run方法用于对这个task内储存的func进行异步的调用。平时一般用不到,因为task是一个基本单位,只封装了一个函数,直接调用的话,没什么成效。我们一般用event
的run函数来对所有task进行并发。
为了方便直接调用,我们把run方法设为同步形式。用它作为一个间接调用异步形式runTask的方法。也就是说它的代码只有这两行:
Copy def run(self):
asyncio.run(self.runTask())
如果您想以异步形式运行这个run,您可以使用另外的runTask方法。但是您不能直接调用它,您可以
或
Copy import asyncio
asyncio.run(task.runTask())
event
event,顾名思义,也就是事件。我们称它的对象为事件流。一个事件由什么构成?答:由任务构成。所以,event是多个task的载体,它可以对多个task进行集中操作、集体并发和以自己为返回值载体为多个task建立数据关联。结合上文提到的turingAPI异步模型核心思想,我们可以把event看作第二层的封装。
event的所有成员变量如下:
Copy tasks=[] #事件流所承载的所有task序列
name=None #事件流的名称,在进行数据互补时,这是多个event和task唯一辨认每一个event的方式
returns={} #在并发task后所有拥有名称并正确指向该事件流的task的返回值。键为task名称,值为返回
#值(可能经过returnFunc的处理)
anonymousReturns=[] #在并发task后所有没有名称且正确指向该事件流的task的返回值序列
association = {} #在进行数据互补时,所有task的互补信息。格式为
#需要关联的taskName : [关联到的eventName1 , eventName2.....]
eventPool=None #所属事件池
__init__方法
也不多赘述了,上代码吧。
Copy def __init__(self,tasks=[],name=None,association={},eventPool=None):
self.tasks=tasks
self.name=name
self.association=association
self.eventPool=eventPool
run方法
run方法可以对该事件流所承载的所有task进行并发。您可以使用同步的方式调用这个方法。
如果您想要以异步形式调用这个方法,您可以使用runEvent方法。但您不能直接调用它,您可以:
Copy await event.runEvent()
或
Copy import asyncio
asyncio.run(event.runEvent())
注意,returns和anonymousReturns成员在一次并发后不会被消除,而是一直保留着数据,直到下一次并发才会将数据清空。
除此之外,我们还在event外部为您提供了三个用于快捷添加、查找、删除task的方法,如下:
addTask deleteName findTask
用于快速生成一个task,并对一个指定的事件流添加该task到tasks序列。
函数构造:
Copy def addTask(event,func,name=None,args=[],kwargs={},returnFunc=None,association=None,associationFunc=None)
event:要将task添加到的事件流
func:生成的task的func成员
name:生成的task的name成员
args:生成的task的args成员
kwargs:生成的task的kwargs成员
returnFunc:生成的task的returnFunc成员
association:生成的task的association成员
associationFunc:生成的task的associationFunc成员
方法名称其实原本想要写deleteTask的,不小心写错了,然后又不小心发布了
deleteName方法用于在一个事件流的tasks序列中删除一个有名称的task。该方法只会在事件流中与其断绝关联,但是task的event成员需要您自己重新设置。
函数构造:
Copy def deleteName(event,taskName)
event:指定的事件流
taskName:要删除的task的name成员,即其名称
findTask用于在一个事件流的tasks序列中进行寻找一个拥有名称的task的索引(下标)。
函数构造:
Copy def findTask(event,taskName)
event:指定事件流
taskName:需要查找的task的名称
如果找到,返回这个task在事件流tasks序列中的索引;
如果没有找到,返回假。
eventPool
eventPool,顾名思义,就是事件池。将多个事件流存在一个载体中,就形成了一个事件池。在下文我们称eventPool的对象为事件池。事件池可以对多个事件流进行有选择性的集体并发,并集中一处管理所承载的事件流。除此之外,它还可以作为一个中间载体,让多个event之间进行数据互补。这就是turingAPI异步模型核心思想中的第三层封装。
以下是eventPool的所有成员变量:
Copy events=[] #所承载的事件流序列
eventReturns={} #在并发事件流后所有有名称并正确指向该事件池的事件流的返回值,格式为
#事件流名称 : [事件流returns成员,事件流anonymousReturns成员]
indexs={} #每个拥有名称的事件流在events序列中的索引,格式为
#事件流名称 : 在events序列中的索引
run方法:
run方法可以对该事件池所承载的事件流进行有选择性的并发。您可以通过同步形式调用它。
函数构造:
Copy def run(self,nameList=[],indexList=[])
nameList:需要进行并发的所有事件流名称
indexList:需要进行并发的所有事件流索引
两个参数可以叠加使用,两个都不填默认为并发所有。
如果您想要以异步形式run该事件池,您可以使用runEvents。它是异步的,参数设置与run相同。您可以使用以下方式调用它。
Copy await eventPool.runEvents(xxxx,xxxx)
Copy import asyncio
aysncio.run(eventPool.runEvents(xxxx,xxxx))
我们在eventPool类外还提供了三个用于增加、删除、查找事件流的顶级API
addEvent deleteEvent findEvent
addEvent方法用于快速创建一个事件流并将其加入指定事件池的events序列。
函数构造:
Copy def addEvent(eventPool,tasks=[],name=None,association={})
eventPool :要添加到的指定事件池
tasks : 事件流 tasks成员
name : 事件流名称
association : 事件流association成员
deleteEvent用于在一个事件池的events序列中删除一个拥有名称的指定事件流。
不会删除indexs中对该事件流的索引键值对。
函数构造:
Copy def deleteEvent(eventPool,eventName)
eventPool:指定事件池
eventName:要删除的事件流的名称
findEvent用于查找一个指定的有名字的事件流在指定事件池的events序列中的索引。
函数构造:
Copy def findEvent(eventPool,eventName)
eventPool:指定的事件池
eventName:要删除的事件名称
示例:
Copy import turingAPI
user = turingAPI.icodeUser('your cookie')
ev = turingAPI.event(name='view')
for i in range(100):
turingAPI.addTask(ev,user.getWorkDetail,'get{num}'.format(i),['a work id'],{})
ev.run()