百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术资源 > 正文

在Python中使用Asyncio系统(3-8)?在关闭过程中等待执行器完成

off999 2024-11-21 19:22 29 浏览 0 评论

在关闭期间等待执行器

在前几节“快速入门”介绍了基本executor接口示例3-3,幸好阻塞的time.sleep()调用比asyncio.sleep()时间更短。因为这意味着执行器任务比main()协程完成得更快,因此程序正确地关闭了。

本节检查当执行器作业的完成时间比所有挂起的Task实例都长时,在关闭期间会发生什么。简单的回答是:如果不进行干预,你会看到像示例3-36中的main()代码所产生的错误。

示例 3-36 执行器需要太长时间才能完成

# quickstart.py
import time
import asyncio


async def main():
    loop = asyncio.get_running_loop()
    loop.run_in_executor(None, blocking)
    print(f'{time.ctime()} Hello!')
    await asyncio.sleep(1.0)
    print(f'{time.ctime()} Goodbye!')


def blocking():
    time.sleep(1.5)  
    print(f"{time.ctime()} Hello from a thread!")


asyncio.run(main())
  • (L13) 这个代码示例与示例3-3中的代码完全相同,除了阻塞函数中的休眠时间现在比异步函数中的更长。

运行这段代码会产生以下输出:

$ python quickstart.py
Fri Jan 24 16:25:08 2020 Hello!
Fri Jan 24 16:25:09 2020 Goodbye!
exception calling callback for <Future at [...snip...]>
Traceback (most recent call last):
<big nasty traceback>
RuntimeError: Event loop is closed
Fri Jan 24 16:25:09 2020 Hello from a thread!

幕后发生的情况是,run_in_executor()不创建Task实例:它只是返回一个Future。这意味着它不包含在asyncio.run()中被取消的“活跃任务”集合中,因此在asyncio.run()中调用的run_until_complete()不用等待执行器任务完成。在asyncio.run()中调用内部loop.close()会引发RuntimeError。

在写这本书的时候,Python 3.8中的loop.close()并不等待所有执行器作业完成,这就是为什么从run_in_executor()返回的Future会报出问题:当它解析时,循环已经关闭。在核心Python开发团队中有关于如何改进这一点的讨论,但在解决方案确定之前,你需要一种处理这些错误的策略。

建议:在Python 3.9中,asyncio.run()函数已得到改进,可以正确地等待执行程序关闭,但在写本文时,还没有将其反向移植到Python 3.8中。

修正这个问题的几个想法,都有不同的取舍,我们将看看其中的几个。我的这个练习的真正目标是帮助你从不同的角度考虑事件循环的生命周期,考虑在一个重要程序中可能进行互操作的所有协程、线程和子进程的生命周期管理。

第一个想法,也是最容易实现的,如例3-37所示,就是总是在协程内部等待一个执行器任务。

示例 3-37 选项A:把执行器调用封装到一个协程中

# quickstart.py
import time
import asyncio
from concurrent.futures import ThreadPoolExecutor as Executor


async def main():
    loop = asyncio.get_running_loop()
    future = loop.run_in_executor(None, blocking)  
    try:
        print(f'{time.ctime()} Hello!')
        await asyncio.sleep(1.0)
        print(f'{time.ctime()} Goodbye!')
    finally:
        await future  


def blocking():
    time.sleep(2.0)
    print(f"{time.ctime()} Hello from a thread!")


try:
    asyncio.run(main())
except KeyboardInterrupt:
    print('Bye!')
  • (L8) 这个想法的目的是修复run_in_executor()只返回一个Future实例而不是一个任务的缺点。我们不能在asyncio.run()中使用all_tasks()捕获作业,但是我们可以在future上使用await。计划的第一部分是在main()函数中创建一个future。
  • (L14) 我们可以使用try/finally结构来确保在main()函数返回之前等待future函数完成。

代码可以运行,但是它对执行器函数的生命周期管理有很大的限制:这意味着你必须在创建执行器作业的每个范围内使用try/finally。我们更喜欢以创建异步任务的方式生成执行器作业,并且还让asyncio.run()内部的关机处理执行一个优雅的退出操作。

下一个想法,如例3-38中显示的,这个有点巧妙。因为我们的问题是一个执行器创建一个future而不是一个task,并且asyncio.run()中的关闭处理处理任务,所以我们的下一个计划是将future(由执行器产生)包装在一个新的task对象中。

示例 3-38 选项B:将执行器future添加到收集的任务中

# quickstart.py
import time
import asyncio
from concurrent.futures import ThreadPoolExecutor as Executor


async def make_coro(future):  
    try:
        return await future
    except asyncio.CancelledError:
        return await future


async def main():
    loop = asyncio.get_running_loop()
    future = loop.run_in_executor(None, blocking)
    asyncio.create_task(make_coro(future))  
    print(f'{time.ctime()} Hello!')
    await asyncio.sleep(1.0)
    print(f'{time.ctime()} Goodbye!')


def blocking():
    time.sleep(2.0)
    print(f"{time.ctime()} Hello from a thread!")


try:
    asyncio.run(main())
except KeyboardInterrupt:
    print('Bye!')
  • (L15) 我们获取run_in_executor()调用返回的future,并把它传递给一个新的功能函数make_coro()。这里重要的一点是,我们正在使用create_task(),这意味着该任务会出现在asyncio.run()要处理关闭的all_tasks()列表中,并将在关闭过程中收到一个取消请求。
  • (L6) 这个功能函数make_coro()只是简单地等待future完成,但至关重要的是,即使在CancelledError的异常处理程序中它也继续等待future完成。

这个解决方案在关闭时表现更好,建议你运行这个示例的时候,也就是在打印“Hello!”后立即按下Ctrl-C。关闭步骤还会等待make_coro()退出,这意味着它还将等待执行器作业退出。但是,这段代码非常笨拙,因为必须将每个执行器Future实例包装在make_coro()调用中。

如果我们愿意放弃asyncio.run()函数的便利性(直到Python3.9才能用),我们可以通过自定义循环处理做得更好一点,如示例3-39所示。

示例 3-39 选项C:就像露营一样,带上你自己的循环和执行器

# quickstart.py
import time
import asyncio
from concurrent.futures import ThreadPoolExecutor as Executor


async def main():
    print(f'{time.ctime()} Hello!')
    await asyncio.sleep(1.0)
    print(f'{time.ctime()} Goodbye!')
    loop.stop()


def blocking():
    time.sleep(2.0)
    print(f"{time.ctime()} Hello from a thread!")


loop = asyncio.get_event_loop()
executor = Executor()  
loop.set_default_executor(executor)  
loop.create_task(main())
future = loop.run_in_executor(None, blocking)  
try:
    loop.run_forever()
except KeyboardInterrupt:
    print('Cancelled')
tasks = asyncio.all_tasks(loop=loop)
for t in tasks:
    t.cancel()
group = asyncio.gather(*tasks, return_exceptions=True)
loop.run_until_complete(group)
executor.shutdown(wait=True)  
loop.close()
  • (L17) 这一次,我们创建自己的执行器实例。
  • (L18) 我们必须将自定义执行器设置为循环的默认执行器。这意味着,只要代码调用在run_in_executor()中运行,它就会使用我们的自定义实例。
  • (L20) 与前面一样,我们运行blocking函数。
  • (L30) 最后,我们可以显式地等待所有执行器作业完成,然后再关闭循环。这将避免我们以前看到的“Event loop is closed”消息。我们可以这样做,因为我们可以访问执行器象;默认执行器未在asyncio API中公开,这就是为什么我们无法对它调用shutdown(),并被迫创建自己的执行器实例。

最后,我们有一个具有普遍适用性的策略:你可以在任何地方调用run_in_executor(),即使在所有异步任务完成后执行器作业仍在运行,你的程序还是会明确地关闭。

我强烈建议你亲手试试使用这个章节里显示的代码示例,尝试不同的策略来创建任务和执行器作业,及时错开它们,并尝试明确地关闭它们。我希望Python的未来版本将允许asyncio.run()函数等待执行器作业完成,但我希望本节中的讨论对你开发明确清晰的关机处理程序的思路会有一定的帮助。

相关推荐

笔记本电脑选哪个品牌比较好

1、苹果APPLE/美国2、戴尔DELL/美国3、华为HUAWEI/中国4、小米MI/中国5、微软Microsoft/美国6、联想LENOVO/中国7、惠普HP/美国8、华硕ASUS/...

10系列显卡排名(10系显卡性能排行)

十系显卡指NVIDIAGeForce10系列,是英伟达研发并推出的图形处理器系列,被用以取代NVIDIAGeForce900系列图形处理器。新系列采用帕斯卡微架构来代替之前的麦克斯韦微架构,并...

最新win7系统下载(windows7最新版本下载)
最新win7系统下载(windows7最新版本下载)

最简单的方法就是,下载完镜像文件后,直接把镜像文件解压,解压到非C盘,然后在解压文件里面找到setup.exe,点击运行即可。安装系统完成后,在C盘找到一个Windows.old(好几个GB,是旧系统打包在这里,垃圾文件了)删除即可。扩展资...

2026-01-15 06:43 off999

哪个电脑管家软件好用(哪个电脑管家好用些)

腾讯电脑管家吧,因为这个是杀毒和管理合一的,占用内存小,因此显得更为简洁,使电脑运行更加流畅此外电脑诊所,工具箱以及4+1的杀毒模式让腾讯电脑管家也收到了广泛的关注4+1杀毒引擎,管家反病毒引擎、金山...

怎么进入win7安全模式(怎么进入win7安全模式界面)

方法如下:1、首先进入Win7系统,然后使用Win键+R组合键打开运行框,输入“Msconfig”回车进入系统配置。2、在打开的系统配置中,找到“引导”选项,然后单击,选择Win7的引导项,然后在“安...

怎么分区固态硬盘(怎样分区固态硬盘)

固态硬盘的分区方法与传统机械硬盘基本相同,以下是一个简单的步骤:1.打开磁盘管理工具:在Windows操作系统中,按下Win+X键,选择"磁盘管理"。或者打开控制面板,在"...

笔记本声卡驱动怎么下载(笔记本如何下载声卡)
笔记本声卡驱动怎么下载(笔记本如何下载声卡)

1、在浏览器中输入并搜索,然后下载并安装。2、安装完成后打开360驱动大师,它就会自动检测你的电脑需要安装或升级的驱动。3、检测完毕后,我们可以看到我们的声卡驱动需要安装或升级,点击安装或升级,就会开始自动安装或升级声卡了。4、升级过程中会...

2026-01-15 05:43 off999

win10加快开机启动速度(加快开机速度 win10)

一、启用快速启动功能1.按win+r键调出“运行”在输入框输入“gpedit.msc”按回车调出“组策略编辑器”?2.在“本地组策略编辑器”依次打开“计算机配置——管理模块——系统——关机”在右侧...

excel的快捷键一览表(excel的快捷键一览表超全)
excel的快捷键一览表(excel的快捷键一览表超全)

Excel快捷键大全的一些操作如下我在工作中经常使用诸如word或Excel之类的办公软件。我相信每个人都不太熟悉这些办公软件的快捷键。使用快捷键将提高办公效率,并使您的工作更加轻松快捷。。例如,在复制时,请使用CtrI+C进行复制,...

2026-01-15 05:03 off999

华硕u盘启动按f几(华硕u盘装系统按f几进入)

F8。1、开机的同时按F8进入BIOS。2、在Boot菜单中,置secure为disabled。3、BootListOption置为UEFI。4、在1stBootPriority中usb—HD...

bootmgr(bootmgrismissing开机不了怎么办)
  • bootmgr(bootmgrismissing开机不了怎么办)
  • bootmgr(bootmgrismissing开机不了怎么办)
  • bootmgr(bootmgrismissing开机不了怎么办)
  • bootmgr(bootmgrismissing开机不了怎么办)
手机云电脑怎么用(手机云端电脑)

使用手机云电脑,您首先需要安装相应的云电脑应用。例如,华为云电脑APP。在安装并打开应用后,您将看到一个显示器的图标,这就是您的云电脑。点击这个图标,您将被连接到一个预装有Windows操作系统和必要...

ie11浏览器怎么安装(ie11浏览器安装步骤)

如果IE浏览器11版本你发现无法正常安装,那么很可能是这样几个原因,一个就是电脑的存储空间不够到时无法安装,再有就是网络的问题,如果没有办法安装的话就不要再安装了,本身这个IE浏览器并不是多好用,你最...

台式机重装系统win7(台式机怎么重装win7)

下面主要介绍两种方法以重装系统:一、U盘重装系统准备:一台正常开机的电脑和一个U盘1、百度下载“U大师”(老毛桃、大白菜也可以),把这个软件下载并安装在电脑上。2、插上U盘,选择一键制作U盘启动(制作...

字母下划线怎么打出来(字母下的下划线怎么去不掉)

第一步,在电脑上找到文字处理软件WPS,双击即自动新建一个新文档。第二步,在文档录入需要处理的字母和数字,双击鼠标或拖动鼠标选择要处理的内容。第三步,在页面的左上方的横向菜单栏,找到字母U的按纽,点击...

取消回复欢迎 发表评论: