一、异步方法依然会假死(freezing)

什么是程序的假死,这里不再多描述,特别是在编写桌面程序的时候,如果是使用单个线程,同步函数的方式,假死是不可避免的,但是有时候我们即使是使用了异步函数的方式依然是不可避免的,依然会假死,这是为什么呢,下面会通过几个例子来详细说明。

1、一般程序的调用方“假死”


import asyncio
import time
import threading#定义一个异步操作
async def hello1(a,b):print(f"异步函数开始执行")await asyncio.sleep(3)print("异步函数执行结束")return a+b#在一个异步操作里面调用另一个异步操作
async def main():c=await hello1(10,20)print(c)print("主函数执行")loop = asyncio.get_event_loop()
tasks = [main()]
loop.run_until_complete(asyncio.wait(tasks))loop.close()'''运行结果为:
异步函数开始执行(在此处要等待3秒)
异步函数执行结束
30
主函数执行
'''

注意一个问题:我们前面所讲的例子中,没有出现等待,是因为各个异步方法之间是“完全并列”关系,彼此之间没有依赖,所以,我可以将所有的异步操作“gather”起来,然后通过事件循环,让事件循环在多个异步方法之间来回调用,永不停止,故而没有出现等待。

但是,现实中不可能所有的异步方法都是完全独立的,没有任何关系的,在上面的这个例子中,就是很好的说明,hello1是一个耗时任务,耗时大约3秒,main也是一个异步方法,但是main中需要用到hello1中的返回结果,所以他必须要等待hello1运行结束之后再才能继续执行,这就是为什么会得到上面结果的原因。这也再一次说明,异步依然是会有阻塞的。

我们也可以这样理解,因为我给事件循环只注册了一个异步方法,那就是main,当在main里面遇到了await,事件循环挂起,转而寻找其他的异步方法,但是由于只注册了一个异步方法给事件循环,他没有其他的方法可执行了,所以只能等待,让hello1执行完了,再继续执行。

2、窗体程序的假死

(1)同步假死

import tkinter as tk          # 导入 Tkinter 库
import timeclass Form:def __init__(self):self.root=tk.Tk()self.root.geometry('500x300')self.root.title('窗体程序')  #设置窗口标题self.button=tk.Button(self.root,text="开始计算",command=self.calculate)self.label=tk.Label(master=self.root,text="等待计算结果")self.button.pack()self.label.pack()self.root.mainloop()def calculate(self):time.sleep(3)  #模拟耗时计算self.label["text"]=300if __name__=='__main__':form=Form()

运行的结果就是,我单机一下“开始计算”按钮,然后窗体会假死,这时候无法移动窗体、也无法最大化最小化、3秒钟之后,“等待计算结果”的label会显示出3,然后前面移动的窗体等操作接着发生,最终效果如下:

上面的窗体会假死,这无可厚非,因为,所有的操作都是同步方法,只有一个线程,负责维护窗体状态的线程和执行好使计算的线程是同一个,当遇到time.sleep()的时候自然会遇到阻塞。那如果我们将耗时任务换成异步方法呢?代码如下:

(2)异步假死

import tkinter as tk          # 导入 Tkinter 库
import asyncioclass Form:def __init__(self):self.root=tk.Tk()self.root.geometry('500x300')self.root.title('窗体程序')  #设置窗口标题self.button=tk.Button(self.root,text="开始计算",command=self.get_loop)self.label=tk.Label(master=self.root,text="等待计算结果")self.button.pack()self.label.pack()self.root.mainloop()#定义一个异步方法,模拟耗时计算任务async def calculate(self):await asyncio.sleep(3)self.label["text"]=300#asyncio任务只能通过事件循环运行,不能直接运行异步函数def get_loop(self):self.loop=asyncio.get_event_loop()self.loop.run_until_complete(self.calculate())self.loop.close()if __name__=='__main__':form=Form()

我们发现,窗体依然会造成阻塞,情况和前面的同步方法是一样的,为什么会这样呢?因为这个地方虽然启动了事件循环,但是拥有事件循环的那个线程同时还需要维护窗体的状态,始终只有一个线程在运行,当单击“开始计算”按钮,开始执行get_loop函数,在get_loop里面启动异步方法calculate,然后遇到await,这个时候事件循环暂停,但是由于事件循环只注册了calculate一个异步方法,也没其他事情干,所以只能等待,造成假死阻塞。

解决办法就是我专门再创建一个线程去执行一些计算任务,维护窗体状态的线程就之专门负责维护状态,后面再详说。

二、多线程结合asyncio解决调用时的假死

1、asyncio专门实现Concurrency and Multithreading(多线程和并发)的函数介绍

为了让一个协程函数在不同的线程中执行,我们可以使用以下两个函数

(1)loop.call_soon_threadsafe(callback, *args),这是一个很底层的API接口,一般很少使用,本文也暂时不做讨论。

(2)asyncio.run_coroutine_threadsafe(coroutine,loop)

第一个参数为需要异步执行的协程函数,第二个loop参数为在新线程中创建的事件循环loop,注意一定要是在新线程中创建哦,该函数的返回值是一个concurrent.futures.Future类的对象,用来获取协程的返回结果。

future = asyncio.run_coroutine_threadsafe(coro_func(), loop)   # 在新线程中运行协程

result = future.result()   #等待获取Future的结果

2、不阻塞的多线程并发实例

asyncio.run_coroutine_threadsafe(coroutine,loop)的意思很简单,就是我在新线程中创建一个事件循环loop,然后在新线程的loop中不断不停的运行一个或者是多个coroutine。参考下面代码:

import asyncio import asyncio,time,threading#需要执行的耗时异步任务
async def func(num):print(f'准备调用func,大约耗时{num}')await asyncio.sleep(num)print(f'耗时{num}之后,func函数运行结束')#定义一个专门创建事件循环loop的函数,在另一个线程中启动它
def start_loop(loop):asyncio.set_event_loop(loop)loop.run_forever()#定义一个main函数
def main():coroutine1 = func(3)coroutine2 = func(2)coroutine3 = func(1)new_loop = asyncio.new_event_loop()                        #在当前线程下创建时间循环,(未启用),在start_loop里面启动它t = threading.Thread(target=start_loop,args=(new_loop,))   #通过当前线程开启新的线程去启动事件循环t.start()asyncio.run_coroutine_threadsafe(coroutine1,new_loop)  #这几个是关键,代表在新线程中事件循环不断“游走”执行asyncio.run_coroutine_threadsafe(coroutine2,new_loop)asyncio.run_coroutine_threadsafe(coroutine3,new_loop)for i in "iloveu":print(str(i)+"    ")if __name__ == "__main__":main()'''运行结果为:
i    准备调用func,大约耗时3
l    准备调用func,大约耗时2
o    准备调用func,大约耗时1
v
e
u
耗时1之后,func函数运行结束
耗时2之后,func函数运行结束
耗时3之后,func函数运行结束
'''

我们发现,main是在主线程中的,而三个协程函数是在新线程中的,它们是在一起执行的,没有造成主线程main的阻塞。下面再看一下窗体函数中的实现。

3、tkinter+threading+asyncio

import tkinter as tk          # 导入 Tkinter 库
import time
import asyncio
import threadingclass Form:def __init__(self):self.root=tk.Tk()self.root.geometry('500x300')self.root.title('窗体程序')  #设置窗口标题self.button=tk.Button(self.root,text="开始计算",command=self.change_form_state)self.label=tk.Label(master=self.root,text="等待计算结果")self.button.pack()self.label.pack()self.root.mainloop()async def calculate(self):await asyncio.sleep(3)self.label["text"]=300def get_loop(self,loop):self.loop=loopasyncio.set_event_loop(self.loop)self.loop.run_forever()def change_form_state(self):coroutine1 = self.calculate()new_loop = asyncio.new_event_loop()                        #在当前线程下创建时间循环,(未启用),在start_loop里面启动它t = threading.Thread(target=self.get_loop,args=(new_loop,))   #通过当前线程开启新的线程去启动事件循环t.start()asyncio.run_coroutine_threadsafe(coroutine1,new_loop)  #这几个是关键,代表在新线程中事件循环不断“游走”执行if __name__=='__main__':form=Form()

运行上面的代码,我们发现,此时点击“开始计算”按钮执行耗时任务,没有造成窗体的任何阻塞,我可以最大最小化、移动等等,然后3秒之后标签会自动显示运算结果。为什么会这样?

上面的代码中,get_loop()、change_form_state()、__init__()都是定义在主线程中的,窗体的状态维护也是主线程,二耗时计算calculate()是一个异步协程函数。

现在单击“开始计算按钮”,这个事件发生之后,会触发主线程的chang_form_state函数,然后在该函数中,会创建新的线程,通过新的线程创建一个事件循环,然后将协程函数注册到新线程中的事件循环中去,达到的效果就是,主线程做主线程的,新线程做新线程的,不会造成任何阻塞。

4、multithreading+asyncio总结

第一步:定义需要异步执行的一系列操作,及一系列协程函数;

第二步:在主线程中定义一个新的线程,然后在新线程中产生一个新的事件循环;

第三步:在主线程中,通过asyncio.run_coroutine_threadsafe(coroutine,loop)这个方法,将一系列异步方法注册到新线程的loop里面去,这样就是新线程负责事件循环的执行。

三、使用asyncio实现一个timer

所谓的timer指的是,指定一个时间间隔,让某一个操作隔一个时间间隔执行一次,如此周而复始。很多编程语言都提供了专门的timer实现机制、包括C++、C#等。但是 Python 并没有原生支持 timer,不过可以用 asyncio.sleep 模拟。

大致的思想如下,将timer定义为一个异步协程,然后通过事件循环去调用这个异步协程,让事件循环不断在这个协程中反反复调用,只不过隔几秒调用一次即可。

简单的实现如下(本例基于python3.7):

async def delay(time):await asyncio.sleep(time)async def timer(time,function):while True:future=asyncio.ensure_future(delay(time))await futurefuture.add_done_callback(function)def func(future):print('done')if __name__=='__main__':asyncio.run(timer(2,func))'''运行结果为:
done
done
done
done
done
done
done
done
done
done
done
.
.
.
.每隔2秒打印一个done
'''

几个注意点:asyncio.sleep()本身就是一个协程函数,故而可以将它封装成一个Task或者是Future,等待时间结束也就是任务完成,绑定回调函数。当然,本身python语法灵活,上面只是其中一种实现而已。

python协程(4): asyncio结合多线程解决阻塞问题以及timer模拟相关推荐

  1. python asyncio教程_Python 协程模块 asyncio 使用指南

    Python 协程模块 asyncio 使用指南 前面我们通过5 分钟入门 Python 协程了解了什么是协程,协程的优点和缺点和如何在 Python 中实现一个协程.没有看过的同学建议去看看.这篇文 ...

  2. Python 异步 IO 、协程、asyncio、async/await、aiohttp

    From :廖雪峰 异步IO :https://www.liaoxuefeng.com/wiki/1016959663602400/1017959540289152 Python Async/Awai ...

  3. python 异步编程:协程与 asyncio

    文章目录 一.协程(coroutine) 1.1 协程的概念 1.2 实现协程的方式 二.asyncio 异步编程 2.1 事件循环 2.2 快速上手 2.3 运行协程 2.4 await 关键字 2 ...

  4. python协程—asyncio模块

    为什么使用协程? 当多线程或者多进程足够多时,实际上并不能解决性能的瓶颈问题,也就是多线程和多进程对小规模的请求可以提高效率,过多的请求实际上会降低服务资源响应效率,因此协程是更好的解决文案. 什么是 ...

  5. python协程asyncio使用

    协程 协程 (corountine):又称微线程. asyncio,在单线程利用CPU和IO同时执行的原理,实现函数异步执行. 实现协程就是要多个任务的循环,await是挂起命令.每到一个地方awai ...

  6. python 协程 php,python3.x,协程_python协程练习部分代码的理解?,python3.x,协程,asyncio - phpStudy...

    python协程练习部分代码的理解? import asyncio import threading async def wget(host): print('wget {}'.format(host ...

  7. python多线程调用携程,Python 协程,Python携程

    Python 协程,Python携程 协程 进程:操作系统中存在 线程:操作系统中存在 协程:是微线程 模块(greenlet) 协程不是一个真实存在的东西,是由程序员创造出来的 协程,是对一个线程分 ...

  8. 听说过python协程没?听说过 asyncio 库没?都在这一篇博客了

    python 中协程概念是从 3.4 版本增加的,但 3.4 版本采用是生成器实现,为了将协程和生成器的使用场景进行区分,使语义更加明确,在 python 3.5 中增加了 async 和 await ...

  9. python协程实时输出_python协程

    不知道你有没有被问到过有没有使用过的python协程? 协程是什么? 协程是一种用户态轻量级,是实现并发编程的一种方式.说到并发,就能想到了多线程 / 多进程模型,是解决并发问题的经典模型之一. 但是 ...

最新文章

  1. vue点击增加class_Vuevbind动态绑定class
  2. 人人都能学会的python编程教程15:高级特性2
  3. 查看Tomcat使用的版本
  4. 《组合数学》——卡特兰数
  5. 深度模型不work?这有一份超全的Debug检查清单
  6. 实验十:图形用户界面设计
  7. eclipse在线安装ivy和ivyde
  8. Focal loss及其实现
  9. 【leetcode】91. Decode Ways A-Z的字母表示1-26的数字,反向破解多少种字符串的可能性...
  10. ng-show和ng-if的区别和使用场景
  11. 解析word文件的简单实现
  12. 网狐6603服务器文档,【整理发布】网狐 6603 棋牌平台搭建图文详解(二)
  13. 修改firefox的ssh插件的xpi包,hook自已功能
  14. 华为鸿蒙用不用清理内存卡,华为这6个设置必须要关,否则天天清理内存也没用,关了再用5年...
  15. 02中国华南华东华北华中华西等位置画出来
  16. 阿里云ACP级认证考试心得+过关经验
  17. 个人微信/支付宝免签支付系统源码
  18. IDEA出现Perhaps you are running on a JRE rather than a JDK?
  19. Web前端之浅谈css
  20. 汉字转UTF8 16进制字符串

热门文章

  1. scatter函数详解
  2. c语言b6=1什么意思,维生素c加维生素b6功效
  3. 基于噪声伪标签和对抗学习的医学图像分割标注高效学习
  4. 【Python爬虫系列教程 18-100】Python网络爬虫实战:小姐姐手把手教你爬取并下载英雄联盟所有英雄皮肤高清大图
  5. NoClassDefFoundError: io/vov/vitamio/LibsChecker
  6. 深入理解ReLU、Leaky ReLU、 PReLU、ELU、Softplus
  7. Flex自动换行调整上下边距
  8. 怎么在视频上叠加字幕和Logo--开题篇
  9. 小程序 绘制饼状图
  10. 头歌题目:计算指定的年月日,是这一年中的哪一天,Python实现