Python 并发库之 Future类概念与代码展示

480 阅读4分钟

「这是我参与2022首次更文挑战的第24天,活动详情查看:2022首次更文挑战」。

正式的Python专栏第73篇,同学站住,别错过这个从0开始的文章!

上篇,学委展示了线程池,我们看到ThreadPoolExecutor提供了submit方法,提交一个任务。

不过它有返回值,它的返回值是Future类的一个实例对象。

通过这个对象我们可以获取到提交任务的执行结果。

怎么获取呢?请继续往下看。

Future

concurrent.futures.Future这个类是用来获取线程任务返回结果的。

跟Thread类不一样,线程对象运行完毕,它不会给我们线程内部执行的方法的结果,但是Future可以。

Executor调用submit的结果 跟 线程运行的对比

学委准备了下面的代码:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2022/2/23 10:20 下午
# @Author : LeiXueWei
# @CSDN/Juejin/Wechat: 雷学委
# @XueWeiTag: CodingDemo
# @File : futuredemo.py
# @Project : DeepDivePython


import concurrent.futures
import threading
import time
from concurrent.futures import Future
from concurrent.futures.thread import ThreadPoolExecutor
from datetime import datetime


def job01(slogan):
    name = threading.currentThread().name
    print("%s start thread-name %s - slogan %s" % (datetime.now(), name, slogan))
    time.sleep(3)
    print("%s finish thread-name %s" % (datetime.now(), name))
    return "job01 data: " + str(slogan)

#线程池也支持with风格的资源管理模式
with ThreadPoolExecutor() as executor:
    future1: Future = executor.submit(job01, ("持续学习", "持续开发",))
    print("future1:", future1)
    result = future1.result()
    print("result1:", result)
    print("future1:", future1)

t = threading.Thread(target=job01, kwargs={"slogan": ("持续学习", "持续开发")})
result = t.start()
t.join()
print("thread run result:", result)

运行效果如下:

屏幕快照 2022-02-24 下午11.41.43.png

通过运行上面的代码,我们看到线程类start后并不返回任何结果。

尽管我们使用了join,等到线程运行结束result对象也依然是空。

但是future1这个对象却保证了程序必定能够获得线程内任务的执行结果。

另外,这里也补充一下Executor类资源池,也支持with风格,在with代码快结束后,池内资源自动回收。

future的方法展示

future类提供的方法还不少。

  • result : 获取线程调用方法的返回结果。上面展示了。

  • cancel:中途尝试取消executor提交的线程,可能取消不了。

  • done/cancelled/running: 等状态查看方法来探知future对象的不同状态。也就是相应的:任务完成/任务取消/任务运行中。

解释干巴巴的,我们直接看代码会更高效:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2022/2/23 10:20 下午
# @Author : LeiXueWei
# @CSDN/Juejin/Wechat: 雷学委
# @XueWeiTag: CodingDemo
# @File : futuredemo.py
# @Project : DeepDivePython


import concurrent.futures
import threading
import time
from concurrent.futures import Future
from concurrent.futures.thread import ThreadPoolExecutor
from datetime import datetime


def job01(args):
    name = threading.currentThread().name
    print("%s thread-name %s - slogan %s" % (datetime.now(), name, args))
    time.sleep(3)
    return "job01 data: " + str(args)


def job02(args):
    name = threading.currentThread().name
    print("%s thread-name %s - slogan %s" % (datetime.now(), name, args))
    time.sleep(5)
    return "job02 data: " + str(args)


with ThreadPoolExecutor() as executor:
    future1: Future = executor.submit(job01, ("持续学习", "持续开发",))
    future2: Future = executor.submit(job02, ("我是雷学委",))
    cancel_status = future2.cancel()  # 取消线程执行
    print("future1:", future1)
    print("展示future对象各个方法:future2:%s, status:%s, running:%s, cancelled:%s, done:%s" % (
        future2, cancel_status, future2.running(), future2.cancelled(), future2.done()
    ))
    #future2.set_running_or_notify_cancel()
    #future2.set_exception(InterruptedError("Fail")) 
    result = future1.result()
    print("result1:", result)
    result = future2.result()
    print("result2:", result)
    print("future1:", future1)
    print("future2:", future2)

上面学委提交了两次分别submit job01和job02,依次获取了future1,future2对象 。

代码运行结果如下:

屏幕快照 2022-02-25 上午12.49.24.png

然后尝试对future2代表的任务发送一个cancel信号,但是很遗憾,本次cancel没有成功。

future2背后的任务依然正常运行,后面还获取到了符合预期的返回值。

如果我们把这行代码取消注释,我们会看到future2的result返回(抛出)一个异常。

future2.set_exception(InterruptedError("Fail"))

另外还有提供钩子,可以给我们在返回结果前进行回调,具体如下:

#雷学委demo代码
future3: Future = executor.submit(job02, ("我是雷学委",))
future3.add_done_callback(lambda x:print("下次再见,拜拜!"))
print("future3:",future3.result())

总结

Future类提供了很多方法,除了让我们友好的通过它获取到一个异步任务的结果。(用同步的方式获得)。

如果我们调用result传入一个时间,那么也可以不必等待,程序会在timeout的时候推出,这时候我们需要进行异常处理,后面在其他并发类中继续分享。

另外,还可以让我们对它进行监控(各种状态函数)和钩子,实现对池内并发任务的灵活管理。

喜欢Python的朋友,请关注学委的 Python基础专栏 or Python入门到精通大专栏

持续学习持续开发,我是雷学委!
编程很有趣,关键是把技术搞透彻讲明白。
欢迎关注微信,点赞支持收藏!