iT邦幫忙

2

python的asyncio模組(五):Future對象與Task對象

前言

這篇文章需要對javascript的promise有基本的認識,對不熟的讀者可能不太友善,需要自行google,請大家海涵orz

上一篇教學python的asyncio模組(四):Event loop常用API講了關於Event loop常用的幾個method,其中介紹了兩個method:

  1. loop.create_task(coro)
  2. loop.create_future()

這兩個method一個回傳task對象,一個回傳future對象,上一篇教學我們把他視為類似的東西,但仔細研究後這兩種對象在意義與用途上其實差異不小。

事實上這兩個對象確實是有相似的結構,Task對象是從Future對象繼承過來的,所以Future對象所擁有的method,Task對象也有,但是這兩個對象被發明出來的目的是很不一樣的。

Future對象的意義

我們大概都了解,創立一個類別的目的,就是將一堆流程與行為或是事物之間的關係抽象化為一個概念,所以一個好的類別,能夠完好的描述一個概念,並能描述這個概念之下相關連的一些行為。

那Future對象是用來描述怎樣的一個概念呢?

顧名思義,future代表未來,很多其他教學文件說Future對象代表一個還未執行或還未完成的任務的結果,而這的確也指涉了一個未來的概念,這是一個在未來才會出現的結果。

也可以從Javascript的Promise來思考這件事,Promise代表一個未來才會完成的操作,或是一個承諾,看看他有兩個method:then還有catch,相當於:

  1. 我承諾未來完成某操作後會再繼續完成某事 --> then method在做的事情
  2. 我承諾未來若某操作失敗會做怎樣的處理 --> catch method在做的事情

而這也代表著一種未來的概念,或也能說他代表一個未完成的任務,因此他和Future對象想要表達的是很類似的東西。

那這個代表未來的或未完成任務的Future對象會有什麼method呢?

若從一個未完成任務的角度來看,我們對這個Future對象最關心的就是,到底他完成後會得到什麼?會成功還是失敗?完成之後我們還會做什麼?

所以他的method都圍繞這幾個問題而設計:

  1. 觀察現在Future的狀態

    • done() 察看這個任務是否已經完成(成功或失敗)
    • cancelled() 察看這個任務是否已經被取消
  2. 指定任務的結果

    • cancel() 取消任務的執行
    • set_result() 判定任務成功,並指定執行完的結果
    • set_exception() 判定任務失敗,並指定途中出現的exception
  3. 取得任務結果

    • result() 取得任務成功時的結果,若任務未成功則為None
    • exception() 取得任務失敗時的exception,若任務未失敗則為None
  4. 指定任務完成後續要進行的行為

    • add_done_callback() 指定若任務完成後要執行的callback
    • remove_done_callback() 取消若任務完成後要執行的callback

而Future在實務上的主要用途是設計異步程式,Event_loop會拿到很多待完成和未完成的任務,並一遍又一遍的進行輪詢,而這些任務都要以Future對象的結構加進Event_loop裏面。

Task對象的意義

上面在講Future的時候發現完全沒有提到Coroutine,這是因為Future的method裏面完全沒有使用到Coroutine,雖然Future是緊貼著asyncio的Event loop而設計的,但這不表示Future需要Coroutine,甚至asyncio的Event loop也不一定要Coroutine才能完成異步程式喔!

Future充其量只是一個描述概念的框架,也制定了一些能被Event loop所使用的基本方法,而Task對象繼承了Future對象的一些基本method,另外其在執行__init__進行初始化的時候,會多傳入一個Coroutine參數。

仔細研究原始碼會發現Task對象裏面有一個非常重要的method叫作_step,他扮演了Coroutine和Event loop的溝通橋樑,對內負責Coroutine的執行,對外又因為繼承了Future的method所以能被Event loop所使用。

簡而言之,Task對象有著Future對象的外殼,能被Event loop所使用,對內又能嵌入Coroutine,讓Coroutine成為這個未完成任務的實際內容。

至於更詳細一點的說明之後會再開一個asyncio源碼解析的系列文章,這裡就不深究下去了。

Future對象的使用

前面講了一堆概念性的東西,現在用code示範一下Future對象不需要Coroutine也能夠正常運行。

下面我們用Future來簡單模擬javascript裡的promise物件吧!

let promise_example = (success_or_fail) => {
    return new Promise((resolve, reject) => {
        console.log("Start exec promise_example, success_or_fail === "+success_or_fail);
        setTimeout(()=> {
            if (success_or_fail === 'success') {
                resolve('success');
            } else if (success_or_fail === 'fail'){
                reject(new Error('fail'));
            }
        }, 1000);
    });
}

let success_promise = promise_example('success');
let fail_promise = promise_example('fail');

success_promise.then((value) => {
    console.log('exec success_promise resolve callback');
    console.log(value);
}).catch((err) => {
    console.log('exec success_promise reject callback');
    console.log(err.message);
});

fail_promise.then((value) => {
    console.log('exec fail_promise resolve callback');
    console.log(value);
}).catch((err) => {
    console.log('exec fail_promise reject callback');
    console.log(err.message);
});

上面的Javascript程式創建了一個可以產生兩種promise的函數promise_example,其中success_promise會在停住一秒後resolve('success'),另一個fail_promise在停住一秒後reject('fail')。

然後分別對兩種promise串上一個then method和catch method,接下來就不詳述promise的原理了,懇請不熟悉promise的讀者查一下網路的其他教學XD,我們直結揭曉程式執行結果:

Start exec promise_example, success_or_fail === success
Start exec promise_example, success_or_fail === fail
exec success_promise resolve callback
success
exec fail_promise reject callback
fail

那如果要用asyncio的Future去實作一模一樣的功能,那會像以下的程式:

# python3.5
# ubuntu 16.04

import asyncio

def promise_example(success_or_fail, future):
    print("Start exec promise_example, success_or_fail === "+success_or_fail)
    future._loop.call_later(1, setTimeout_func, success_or_fail, future)

def setTimeout_func(success_or_fail, future):
    if success_or_fail == 'success':
        future.set_result('success')
    elif success_or_fail == 'fail':
        future.set_exception(Exception('fail'))

def success_callback(future):
    try:
        if future.result() is not None:
            print('exec success_promise resolve callback')
            print(future.result())
    except Exception as e:
        print('exec success_promise reject callback')
        print(e)

def fail_callback(future):
    try:
        if future.result() is not None:
            print('exec fail_promise resolve callback')
            print(future.result())
    except Exception as e:
        print('exec fail_promise reject callback')
        print(e)

loop = asyncio.get_event_loop()
success_future = loop.create_future()
fail_future = loop.create_future()

success_future.add_done_callback(success_callback)
fail_future.add_done_callback(fail_callback)

loop.call_soon(promise_example, 'success', success_future)
loop.call_soon(promise_example, 'fail', fail_future)

loop.run_until_complete(asyncio.wait([success_future, fail_future]))

如果要詳細解說上面提到的python語法,可能會增加過多的篇幅,所以第五篇的系列文就先到這篇吧!剩下的內容會在第六篇繼續探討。

下一篇教學:
python的asyncio模組(六):Future對象與Task對象(二)

參考資料:
python Task對象官方文件說明
https://docs.python.org/3.5/library/asyncio-task.html
Future對象的使用
https://medium.com/@lanf0n/%E5%BE%9E-asyncio-%E9%96%8B%E5%A7%8B-callback-c60a74c54743


尚未有邦友留言

立即登入留言