zoukankan      html  css  js  c++  java
  • Python 中的协程 (5) 无阻塞

    1 异步程序依然会假死 freezing

    1)一般程序的调用方 freezing
    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
    主函数执行
    '''

    上面的例子中,hello1是一个耗时3s的异步任务,main也是一个异步方法,但是main需要调用hello1的返回值,所以必须登台hello1执行完成才能继续执行main,这说明异步也是会有阻塞的。

    而之前定义的异步函数不用等待是因为事件循环将所有的异步操作‘gather’起来,在多个操作间不同的游走切换,来回调用所有没有等待。

    也可以理解为,事件循环只有一个异步操作在处理,没有可以切换执行的目标,所以只能等待当前的操作完成。

    2)窗体程序的freezing

    同步freezing

    import tkinter as tk          # 导入 Tkinter 库
    import time
    
    class 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"]=300
    
    if __name__=='__main__':
        form=Form()

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

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

    2 多线程+asyncio 解决调用时freezing

    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)不阻塞的多线程并发实例

    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函数运行结束
    '''

    tkinter+threading+asyncio

    import tkinter as tk          # 导入 Tkinter 库
    import time
    import asyncio
    import threading
    
    class 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"]=300
    
        def get_loop(self,loop):
            self.loop=loop
            asyncio.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函数,然后在该函数中,会创建新的线程,通过新的线程创建一个事件循环,然后将协程函数注册到新线程中的事件循环中去,达到的效果就是,主线程做主线程的,新线程做新线程的,不会造成任何阻塞。

    第一步:定义需要异步执行的一系列操作,及一系列协程函数; 第二步:在主线程中定义一个新的线程,然后在新线程中产生一个新的事件循环; 第三步:在主线程中,通过asyncio.run_coroutine_threadsafe(coroutine,loop)这个方法,将一系列异步方法注册到新线程的loop里面去,这样就是新线程负责事件循环的执行。

    3 使用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 future
            future.add_done_callback(function)
    
    def func(future):
        print('done')
    
    if __name__=='__main__':
        asyncio.run(timer(2,func))
  • 相关阅读:
    Nmap绕过防火墙&脚本的使用
    Nmap在实战中的高级用法
    kali&BT安装好之后无法上网或者无法获得内网IP
    [转]谈渗透测试方法和流程
    xssless
    国内外有名的安全扫描工具,你知道几个?
    Linux下如果忘记了Mysql的root密码该怎么办?
    JSP手动注入 全
    sqlmap用户手册 [详细]
    Windows系统如何使用sqlmap
  • 原文地址:https://www.cnblogs.com/wang-kai-1994/p/10376574.html
Copyright © 2011-2022 走看看