web-dev-qa-db-ja.com

asyncio:エグゼキューターによって実行された未来をキャンセルすることは可能ですか?

Asyncio呼び出しloop.run_in_executorを使用してExecutorでブロッキング関数を開始し、後でキャンセルしたいのですが、それはうまくいかないようです。

コードは次のとおりです。

import asyncio
import time

from concurrent.futures import ThreadPoolExecutor


def blocking_func(seconds_to_block):
    for i in range(seconds_to_block):
        print('blocking {}/{}'.format(i, seconds_to_block))
        time.sleep(1)

    print('done blocking {}'.format(seconds_to_block))


@asyncio.coroutine
def non_blocking_func(seconds):
    for i in range(seconds):
        print('yielding {}/{}'.format(i, seconds))
        yield from asyncio.sleep(1)

    print('done non blocking {}'.format(seconds))


@asyncio.coroutine
def main():
    non_blocking_futures = [non_blocking_func(x) for x in range(1, 4)]
    blocking_future = loop.run_in_executor(None, blocking_func, 5)
    print('wait a few seconds!')
    yield from asyncio.sleep(1.5)

    blocking_future.cancel()
    yield from asyncio.wait(non_blocking_futures)



loop = asyncio.get_event_loop()
executor = ThreadPoolExecutor(max_workers=1)
loop.set_default_executor(executor)
asyncio.async(main())
loop.run_forever()

上記のコードでは、ブロッキング関数の出力のみが許可されると思います。

blocking 0/5
blocking 1/5

次に、ノンブロッキング関数の出力を確認します。しかし、代わりに、私がキャンセルした後でも、ブロッキングの未来は続きます。

出来ますか?それを行う他の方法はありますか?

ありがとう

編集:asyncioを使用したブロッキングコードと非ブロッキングコードの実行に関する詳細: ブロッキングコードと非ブロッキングコードをasyncioとインターフェイスさせる方法 ==

17
Brendan Maguire

この場合、_concurrent.futures.Future_の動作に依存しているため、実際に実行を開始した後はFutureをキャンセルする方法はありません。 そのドキュメントには次のように記載されています =:

cancel()

通話のキャンセルを試みます。 呼び出しが現在実行中でキャンセルできない場合、メソッドはFalseを返します。それ以外の場合、呼び出しはキャンセルされ、メソッドはTrueを返します。

したがって、キャンセルが成功するのは、タスクがExecutor内でまだ保留中の場合のみです。現在、実際には_asyncio.Future_をラップした_concurrent.futures.Future_を使用しており、実際にはloop.run_in_executor()によって返される_asyncio.Future_はCancellationErrorを発生させます。基になるタスクが実際にすでに実行されている場合でも、cancel()を呼び出した後、_yield from_を試してください。ただし、Executor内のタスクの実行を実際にキャンセルすることはありません。

実際にタスクをキャンセルする必要がある場合は、スレッドで実行されているタスクを中断する従来の方法を使用する必要があります。それを行う方法の詳細は、ユースケースによって異なります。例で示したユースケースでは、_threading.Event_を使用できます。

_def blocking_func(seconds_to_block, event):
    for i in range(seconds_to_block):
        if event.is_set():
            return
        print('blocking {}/{}'.format(i, seconds_to_block))
        time.sleep(1)

    print('done blocking {}'.format(seconds_to_block))


...
event = threading.Event()
blocking_future = loop.run_in_executor(None, blocking_func, 5, event)
print('wait a few seconds!')
yield from asyncio.sleep(1.5)

blocking_future.cancel()  # Mark Future as cancelled
event.set() # Actually interrupt blocking_func
_
18
dano

スレッドはプロセスの同じメモリアドレス空間を共有するため、実行中のスレッドを終了する安全な方法はありません。これが、ほとんどのプログラミング言語が実行中のスレッドを強制終了することを許可しない理由です(この制限の周りには多くの醜いハックがあります)。

Javaはそれを学びました 難しい方法

解決策は、スレッドではなく別のプロセスで関数を実行し、それを正常に終了することです。

Pebble ライブラリは、キャンセルされるFuturesの実行をサポートするconcurrent.futuresと同様のインターフェイスを提供します。

from pebble import ProcessPool

def function(foo, bar=0):
    return foo + bar

with ProcessPool() as pool:
    future = pool.schedule(function, args=[1])

    # if running, the container process will be terminated 
    # a new process will be started consuming the next task
    future.cancel()  
2
noxdafox