从入门到富豪 发表于 2021-10-31 10:19:24

协程封装问题 这个差异疯了, 跪求大佬解答

下面是2个不同的封装方式, 但是线程的使用却有明显的不同,一个是可以乱序的,一个是按顺序执行的,求大神解答。 不知道原因。
import asyncio
import time
async def ceshi01(x,y):
    await asyncio.sleep(y)
    print(f"我是{x},我运行了{y}分钟")

async def yunxing():
    group_a = asyncio.gather(ceshi01('A',5),ceshi01('a',3))#线程a
    group_b = asyncio.gather(ceshi01('b',4),ceshi01('B',2))#线程b
    await group_a
    await group_b
print(asyncio.run(yunxing()))

运行结果是:          结果是按时间排序的, 效率最高。
我是B,我运行了2分钟
我是a,我运行了3分钟
我是b,我运行了4分钟
我是A,我运行了5分钟

另外一个封装方式
import asyncio
import time
async def ceshi01(x,y):
    await asyncio.sleep(y)
    print(f"我是{x},我运行了{y}分钟")

async def yunxing():
    awaitasyncio.gather(ceshi01('A',5),ceshi01('a',3))   
    awaitasyncio.gather(ceshi01('b',4),ceshi01('B',2))
    awaitasyncio.gather(ceshi01('c',1),ceshi01('c',1))

print(asyncio.run(yunxing()))
运行结果是:               这里的结果是按顺序执行的      从a到c   
我是a,我运行了3分钟
我是A,我运行了5分钟
我是B,我运行了2分钟
我是b,我运行了4分钟
我是c,我运行了1分钟
我是c,我运行了1分钟

傻眼貓咪 发表于 2021-10-31 10:44:39

基本上大部分程序语言都是单一线程的,意思就是先做完一个事情,然后才做另外一个事情,这是肯定的。
Python 附有 asyncio 模组里的 async、await 用于解决同步和异步问题,简单的说就是当你执行一个函数时,系统不会等待返回结果,直接运行下一个函数(看似双线程,其实是单线程)
用容易理解方式解说:async 就好象告诉系统,我要同时执行多个函数,不用等到结束函数时才执行另外一个。而 await asyncio.sleep() 如同 time.sleep(),唯一差别就是 await 用于提醒系统,还有多久记得回来看这个函数,不然函数不会结束(RuntimeWarning: coroutine 'xxx' was never awaited)
我的解说肯定没有网路上的好,你也可以自行网路查找解说,解说百百种。

从入门到富豪 发表于 2021-10-31 10:56:07

傻眼貓咪 发表于 2021-10-31 10:44
基本上大部分程序语言都是单一线程的,意思就是先做完一个事情,然后才做另外一个事情,这是肯定的。
Pyth ...

额还是没有回答我上面的实际问题啊,同样await,为啥第二种是按顺序执行的。

傻眼貓咪 发表于 2021-10-31 11:15:12

从入门到富豪 发表于 2021-10-31 10:56
额还是没有回答我上面的实际问题啊,同样await,为啥第二种是按顺序执行的。

这是优先执行问题

第一个封装会先检测外面的 await (外面两个 await 都有联系)然后再检测 ceshi01() 函数里的 await,所以 group_a 和 group_b 是同时检测的。

第二个封装外面的三个 await 并没有联系,所以真正是一个个执行,由上往下

傻眼貓咪 发表于 2021-10-31 11:17:40

本帖最后由 傻眼貓咪 于 2021-10-31 11:23 编辑

从入门到富豪 发表于 2021-10-31 10:56
额还是没有回答我上面的实际问题啊,同样await,为啥第二种是按顺序执行的。

为了避免这种问题,最好创建事件循环 asyncio.get_event_loop() 和 待处理事件列表,想执行时,则用 run_until_complete() 执行便可。如:import asyncio
import time

async def A():
    await asyncio.sleep(3)
    print("我是洗衣机 A")

async def B():
    await asyncio.sleep(1)
    print("我是洗衣机 B")

async def C():
    await asyncio.sleep(2)
    print("我是洗衣机 C")

loop = asyncio.get_event_loop() # 事件循环
task = # 待处理事件

start = time.time()
loop.run_until_complete(asyncio.wait(task)) # 执行所有事件
end = time.time()
loop.close()
print(f"一共耗时 {int(end - start)} 秒")我是洗衣机 B
我是洗衣机 C
我是洗衣机 A
一共耗时 3 秒

kogawananari 发表于 2021-10-31 12:17:28

无论是create_task还是asyncio.gather都会把协程对象及其各种状态打包注册为 Task 对象,
你可以把它的作用当成是放入任务队列中,并返回

    task1 = asyncio.create_task(coro1)
    task2 = asyncio.create_task(coro1)
    await task1
    await task2

无疑是往队列塞进去2个 再清空队列

    awaitasyncio.create_task(coro1)
    awaitasyncio.create_task(coro1)

则是 塞进去了就清空队列

asyncio.gather的返回值是保住了参数顺序的无关乎执行顺序

kogawananari 发表于 2021-10-31 12:24:13

{:10_338:}建议去学一下JavaScript的asyncfunction和Promise对象 你这个自然就通了   它和py的Future对象差不多

我都没怎么看python的asyncio都会了然后去学个fastapi岂不美哉

js只有asyncfunction和其返回的Promise对象两层 毕竟事件循环是自带的缺点是没有sleep函数能卡住进程

而py有asyncfunction 返回coro 然后coro打包成task task是Future的子类 3层结构   事件循环还得手动开启

kogawananari 发表于 2021-10-31 12:26:19

最重要的思想其实就一个   生产者消费者模型 而已

从入门到富豪 发表于 2021-10-31 13:15:09

kogawananari 发表于 2021-10-31 12:17
无论是create_task还是asyncio.gather都会把协程对象及其各种状态打包注册为 Task 对象,
你可以把它的作 ...

清空队列是啥意思,1和2的写法 感觉是对等的啊,

关于gather保持顺序的事情

我专门搞个栗子,gather的列表不是按顺序执行的,假设是按顺序执行的, 结果的返回也不是按顺序返回的, 我这个是因为有个迭代器,才保证取值顺序。
mylist = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']
class AA():
    def __init__(self):
      self.g = self.getone() #生成了迭代器,放在init里面
    def getone(self):
      for i in mylist:
            yield i#返回子集列表,返回的顺序是a—z

    async def xieru(self,id,delay):

            while True:
                await asyncio.sleep(delay)
                # time.sleep(delay)
                print(id,next(self.g))

    async def main(self):
      task1 =asyncio.create_task(self.xieru("AA",4)) #线程AA 执行的内部顺序abc,def,等是固定的
      task2 =asyncio.create_task(self.xieru("BB",2))#线程BB
      task3 = asyncio.create_task(self.xieru("CC",3))#线程CC
      tasks =
      await asyncio.gather(*tasks)#gather返回的列表,并不能按带入的顺序排列,而是按实际执行完成的顺序排列。

if __name__ =='__main__':
    aa = AA()
    asyncio.run(aa.main()) #运行的程序要放在最外面

从入门到富豪 发表于 2021-10-31 13:17:54

傻眼貓咪 发表于 2021-10-31 11:17
为了避免这种问题,最好创建事件循环 asyncio.get_event_loop() 和 待处理事件列表,想执行时,则用 ru ...

目前python采用了高级的 gather来设置,不用手动用loop了,我感觉还是对await的 对象的 理解不够深刻。

kogawananari 发表于 2021-10-31 13:20:09

从入门到富豪 发表于 2021-10-31 13:15
清空队列是啥意思,1和2的写法 感觉是对等的啊,

关于gather保持顺序的事情


import asyncio

async def count(m):
    await asyncio.sleep(3)
    print(m)
    return m
async def main():
    aws =
    print(await asyncio.gather(*aws))
asyncio.run(main())

按顺序返回

傻眼貓咪 发表于 2021-10-31 13:21:25

从入门到富豪 发表于 2021-10-31 13:17
目前python采用了高级的 gather来设置,不用手动用loop了,我感觉还是对await的 对象的 理解不够深刻。

抱歉,帮不到你了,兄弟

kogawananari 发表于 2021-10-31 13:34:13

从入门到富豪 发表于 2021-10-31 13:15
清空队列是啥意思,1和2的写法 感觉是对等的啊,

关于gather保持顺序的事情


import asyncio

async def count1():
    await asyncio.sleep(1)
    print("\n\n----------任务队列里目前有----------")
    for task in asyncio.all_tasks():
      print(task.get_coro())
    print('----------count1任务运行完毕----------\n\n')
async def count2():
    await asyncio.sleep(2)
    print("\n\n----------任务队列里目前有----------")
    for task in asyncio.all_tasks():
      print(task.get_coro())
    print('----------count2任务运行完毕----------\n\n')
async def main():
    '''await asyncio.create_task(count1())
    await asyncio.create_task(count2())'''
    task1 = asyncio.create_task(count1())
    task2 = asyncio.create_task(count2())
    await task1
    await task2
    print('\n----------main任务运行完毕----------\n')
asyncio.run(main())

我帮你把任务改成了输出当前所有任务   你再把注释放出来换成直接await 对比一下

从入门到富豪 发表于 2021-10-31 22:59:54

kogawananari 发表于 2021-10-31 13:34
import asyncio

async def count1():


亲 除了发现这一种比第一种执行更快一些,还是没看出啥啊, 或者说看不懂 。第一种执行时间明显长很多。

kogawananari 发表于 2021-10-31 23:24:50

从入门到富豪 发表于 2021-10-31 22:59
亲 除了发现这一种比第一种执行更快一些,还是没看出啥啊, 或者说看不懂 。第一种执行时间明显长很多。

一个是继发执行 一个是并发执行

输出的任务队列一看就知道区别了

kogawananari 发表于 2021-10-31 23:32:14

从入门到富豪 发表于 2021-10-31 22:59
亲 除了发现这一种比第一种执行更快一些,还是没看出啥啊, 或者说看不懂 。第一种执行时间明显长很多。

继发运行的时候 队列塞进去一个任务马上就把那个任务做了 明显每次只打印了自己和main

并发运行的时候 队列塞进去两个一起执行的执行的时候打印出了自己和main 之间还有其他任务

从入门到富豪 发表于 2021-11-1 10:45:05

kogawananari 发表于 2021-10-31 23:32
继发运行的时候 队列塞进去一个任务马上就把那个任务做了 明显每次只打印了自己和main

并发运行的时候 ...

这个结果我知道呀, 问题是为啥会这样呢, 这个写法执行逻辑或者顺序是如何操作的呢, 毕竟从写法来讲, 2个写法几乎是对等的呀。
页: [1]
查看完整版本: 协程封装问题 这个差异疯了, 跪求大佬解答