web-dev-qa-db-ja.com

コンカレント.futures.ThreadPoolExecutor.map():タイムアウトが機能しない

_import concurrent.futures
import time 

def process_one(i):
    try:                                                                             
        print("dealing with {}".format(i))                                           
        time.sleep(50)
        print("{} Done.".format(i))                                                  
    except Exception as e:                                                           
        print(e)

def process_many():
    with concurrent.futures.ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor: 
        executor.map(process_one,
                range(100),                                                          
                timeout=3)                                                           


if __name__ == '__main__':                                                           
    MAX_WORKERS = 10
    try:
        process_many()
    except Exception as e:                                                           
        print(e)      
_

docs 言う:

返されたイテレータは、__next__()が呼び出され、Executor.map()の最初の呼び出しからtimeout秒後に結果が利用できない場合、_concurrent.futures.TimeoutError_を発生させます。

しかし、ここではスクリプトは例外を発生させず、待機し続けました。助言がありますか?

15
Hao Wang

ドキュメントで指定されているように、タイムアウトエラーは、マップ上で__next__()メソッドを呼び出している場合にのみ発生します。このメソッドを呼び出すには、たとえば、出力をリストに変換できます。

_from concurrent import futures
import threading
import time


def task(n):
    print("Launching task {}".format(n))
    time.sleep(n)
    print('{}: done with {}'.format(threading.current_thread().name, n))
    return n / 10


with futures.ThreadPoolExecutor(max_workers=5) as ex:
    results = ex.map(task, range(1, 6), timeout=3)
    print('main: starting')
    try:
        # without this conversion to a list, the timeout error is not raised
        real_results = list(results) 
    except futures._base.TimeoutError:
        print("TIMEOUT")
_

出力:

_Launching task 1
Launching task 2
Launching task 3
Launching task 4
Launching task 5
ThreadPoolExecutor-9_0: done with 1
ThreadPoolExecutor-9_1: done with 2
TIMEOUT
ThreadPoolExecutor-9_2: done with 3
ThreadPoolExecutor-9_3: done with 4
ThreadPoolExecutor-9_4: done with 5
_

ここでは、n番目のタスクがn秒間スリープするため、タスク2が完了した後にタイムアウトが発生します。


[〜#〜] edit [〜#〜]:完了しなかったタスクを終了したい場合は、-で答えを試すことができます。 this 質問(ただし、ThreadPoolExecutor.map()は使用しません)、または他のタスクからの戻り値を無視して終了させることができます。

_from concurrent import futures
import threading
import time


def task(n):
    print("Launching task {}".format(n))
    time.sleep(n)
    print('{}: done with {}'.format(threading.current_thread().name, n))
    return n


with futures.ThreadPoolExecutor(max_workers=5) as ex:
    results = ex.map(task, range(1, 6), timeout=3)
    outputs = []
    try:
        for i in results:
            outputs.append(i)
    except futures._base.TimeoutError:
        print("TIMEOUT")
    print(outputs)
_

出力:

_Launching task 1
Launching task 2
Launching task 3
Launching task 4
Launching task 5
ThreadPoolExecutor-5_0: done with 1
ThreadPoolExecutor-5_1: done with 2
TIMEOUT
[1, 2]
ThreadPoolExecutor-5_2: done with 3
ThreadPoolExecutor-5_3: done with 4
ThreadPoolExecutor-5_4: done with 5
_
2
TrakJohnson

source (for python 3.7)mapは、次の関数を返します。

_def map(self, fn, *iterables, timeout=None, chunksize=1):
    ...
    if timeout is not None:
        end_time = timeout + time.time()
    fs = [self.submit(fn, *args) for args in Zip(*iterables)]
    # Yield must be hidden in closure so that the futures are submitted
    # before the first iterator value is required.
    def result_iterator():
        try:
            # reverse to keep finishing order
            fs.reverse()
            while fs:
                # Careful not to keep a reference to the popped future
                if timeout is None:
                    yield fs.pop().result()
                else:
                    yield fs.pop().result(end_time - time.time())
        finally:
            for future in fs:
                future.cancel()
    return result_iterator()
_

TimeoutErroryield fs.pop().result(end_time - time.time())呼び出しから発生しますが、その呼び出しに到達するには結果を要求する必要があります。

理論的根拠は、タスクの送信を気にしないということです。タスクが送信され、バックグラウンドスレッドで実行が開始されます。気になるのは、結果をリクエストするときにタイムアウトすることです。これは、タスクを送信し、タスクを送信して限られた時間で終了することを期待するだけでなく、限られた時間内にタスクに結果をリクエストする通常のユースケースです。

後者があなたの目的である場合は、たとえば concurrent.futuresの個別のタイムアウト に示すように、waitを使用できます。

0
Mr_and_Mrs_D