pythonマルチスレッドアプリケーションです。スレッドでasyncioループを実行し、別のスレッドからコールバックとコルーチンをポストしたいのですが、簡単なはずですが、頭がよくありません。 asyncio もの。
私は私が望むことの半分を行う次の解決策にたどり着きました、何でも気軽にコメントしてください:
import asyncio
from threading import Thread
class B(Thread):
def __init__(self):
Thread.__init__(self)
self.loop = None
def run(self):
self.loop = asyncio.new_event_loop()
asyncio.set_event_loop(self.loop) #why do I need that??
self.loop.run_forever()
def stop(self):
self.loop.call_soon_threadsafe(self.loop.stop)
def add_task(self, coro):
"""this method should return a task object, that I
can cancel, not a handle"""
f = functools.partial(self.loop.create_task, coro)
return self.loop.call_soon_threadsafe(f)
def cancel_task(self, xx):
#no idea
@asyncio.coroutine
def test():
while True:
print("running")
yield from asyncio.sleep(1)
b.start()
time.sleep(1) #need to wait for loop to start
t = b.add_task(test())
time.sleep(10)
#here the program runs fine but how can I cancel the task?
b.stop()
したがって、ループの開始と停止は正常に機能します。 create_taskを使用してタスクを作成することを考えましたが、そのメソッドはスレッドセーフではないため、call_soon_threadsafeでラップしました。しかし、タスクをキャンセルできるようにするために、タスクオブジェクトを取得できるようにしたいと思います。 FutureとConditionを使用して複雑なことを行うこともできますが、もっと簡単な方法があるはずですよね。
イベントループ以外のスレッドから呼び出されているかどうかをadd_task
メソッドに認識させる必要があると思います。そうすれば、同じスレッドから呼び出されている場合は、asyncio.async
を直接呼び出すだけで済みます。そうでない場合は、ループのスレッドから呼び出し側のスレッドにタスクを渡すための追加の作業を行うことができます。次に例を示します。
import time
import asyncio
import functools
from threading import Thread, current_thread, Event
from concurrent.futures import Future
class B(Thread):
def __init__(self, start_event):
Thread.__init__(self)
self.loop = None
self.tid = None
self.event = start_event
def run(self):
self.loop = asyncio.new_event_loop()
asyncio.set_event_loop(self.loop)
self.tid = current_thread()
self.loop.call_soon(self.event.set)
self.loop.run_forever()
def stop(self):
self.loop.call_soon_threadsafe(self.loop.stop)
def add_task(self, coro):
"""this method should return a task object, that I
can cancel, not a handle"""
def _async_add(func, fut):
try:
ret = func()
fut.set_result(ret)
except Exception as e:
fut.set_exception(e)
f = functools.partial(asyncio.async, coro, loop=self.loop)
if current_thread() == self.tid:
return f() # We can call directly if we're not going between threads.
else:
# We're in a non-event loop thread so we use a Future
# to get the task from the event loop thread once
# it's ready.
fut = Future()
self.loop.call_soon_threadsafe(_async_add, f, fut)
return fut.result()
def cancel_task(self, task):
self.loop.call_soon_threadsafe(task.cancel)
@asyncio.coroutine
def test():
while True:
print("running")
yield from asyncio.sleep(1)
event = Event()
b = B(event)
b.start()
event.wait() # Let the loop's thread signal us, rather than sleeping
t = b.add_task(test()) # This is a real task
time.sleep(10)
b.stop()
最初に、イベントループのスレッドIDをrun
メソッドに保存します。これにより、add_task
の呼び出しが後で他のスレッドから行われているかどうかを確認できます。非イベントループスレッドからadd_task
が呼び出された場合、call_soon_threadsafe
を使用してコルーチンをスケジュールする関数を呼び出し、次にconcurrent.futures.Future
を使用してタスクをFuture
の結果を待つ呼び出しスレッド.
タスクのキャンセルに関する注意:cancel
に対してTask
を呼び出すと、次にイベントループが実行されるときにコルーチンでCancelledError
が発生します。これは、タスクがラップしているコルーチンが、次に降伏点に達したときに例外のために中止されることを意味します-コルーチンがCancelledError
をキャッチして自身が中止されない限り。また、これは、ラップされる関数が実際に割り込み可能なコルーチンである場合にのみ機能することにも注意してください。たとえば、asyncio.Future
によって返されたBaseEventLoop.run_in_executor
は、実際にはconcurrent.futures.Future
でラップされているため、実際にキャンセルすることはできません。また、基礎となる関数が実際に実行を開始するとキャンセルすることはできません。 。これらの場合、asyncio.Future
はキャンセルされたと言いますが、エグゼキューターで実際に実行されている関数は引き続き実行されます。
編集:Andrew Svetlovの提案に従い、concurrent.futures.Future
ではなくqueue.Queue
を使用するように最初の例を更新しました。
注: asyncio.async
はバージョン3.4.4で廃止されました。代わりに asyncio.ensure_future
を使用してください。
あなたはすべてを正しく行います。タスク停止用makeメソッド
class B(Thread):
# ...
def cancel(self, task):
self.loop.call_soon_threadsafe(task.cancel)
ところであなたhave作成されたスレッドのイベントループを明示的に設定する
self.loop = asyncio.new_event_loop()
asyncio.set_event_loop(self.loop)
asyncio
は、メインスレッドに対してのみ暗黙的なイベントループを作成するためです。
参考までに、このサイトで得たヘルプに基づいて最終的に実装したコードは、すべての機能を必要としなかったので、より単純です。再度、感謝します!
import asyncio
from threading import Thread
from concurrent.futures import Future
import functools
class B(Thread):
def __init__(self):
Thread.__init__(self)
self.loop = None
def run(self):
self.loop = asyncio.new_event_loop()
asyncio.set_event_loop(self.loop)
self.loop.run_forever()
def stop(self):
self.loop.call_soon_threadsafe(self.loop.stop)
def _add_task(self, future, coro):
task = self.loop.create_task(coro)
future.set_result(task)
def add_task(self, coro):
future = Future()
p = functools.partial(self._add_task, future, coro)
self.loop.call_soon_threadsafe(p)
return future.result() #block until result is available
def cancel(self, task):
self.loop.call_soon_threadsafe(task.cancel)
バージョン3.4.4以降、asyncio
には、スレッドからイベントループにコルーチンオブジェクトを送信するための run_coroutine_threadsafe という関数が用意されています。結果にアクセスするか、タスクをキャンセルするために concurrent.futures.Future を返します。
あなたの例を使用して:
@asyncio.coroutine
def test(loop):
try:
while True:
print("Running")
yield from asyncio.sleep(1, loop=loop)
except asyncio.CancelledError:
print("Cancelled")
loop.stop()
raise
loop = asyncio.new_event_loop()
thread = threading.Thread(target=loop.run_forever)
future = asyncio.run_coroutine_threadsafe(test(loop), loop)
thread.start()
time.sleep(5)
future.cancel()
thread.join()