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