web-dev-qa-db-ja.com

「タスクは破棄されましたが、保留中です」と説明してください。

Python 3.4.2

私はasyncioを学んでおり、これを使用してIPCバス、gbulbはdbusをリッスンします。

いくつかのサイドノート:

だから、IPCチャネルで受信メッセージを継続的にリッスンし、メッセージを_listen_to_ipc_channel_layer_。に渡す関数_message_handler_。を作成しました。

SIGTERMとSIGINTも聞いています。そのため、下部にあるコードを実行しているpythonプロセスにSIGTERMを送信すると、スクリプトは正常に終了します。

問題

…次の警告があります。

_got signal 15: exit
Task was destroyed but it is pending!
task: <Task pending coro=<listen_to_ipc_channel_layer() running at /opt/mainloop-test.py:23> wait_for=<Future cancelled>>

Process finished with exit code 0
_

…次のコードで:

_import asyncio
import gbulb
import signal
import asgi_ipc as asgi

def main():
    asyncio.async(listen_to_ipc_channel_layer())
    loop = asyncio.get_event_loop()

    for sig in (signal.SIGINT, signal.SIGTERM):
        loop.add_signal_handler(sig, ask_exit)

    # Start listening on the Linux IPC bus for incoming messages
    loop.run_forever()
    loop.close()

@asyncio.coroutine
def listen_to_ipc_channel_layer():
    """Listens to the Linux IPC bus for messages"""
    while True:
        message_handler(message=channel_layer.receive(["my_channel"]))
        try:
            yield from asyncio.sleep(0.1)
        except asyncio.CancelledError:
            break

def ask_exit():
    loop = asyncio.get_event_loop()
    for task in asyncio.Task.all_tasks():
        task.cancel()
    loop.stop()


if __name__ == "__main__":
    gbulb.install()
    # Connect to the IPC bus
    channel_layer = asgi.IPCChannelLayer(prefix="my_channel")
    main()
_

私はまだasyncioについてほとんど理解していませんが、何が起こっているのか知っていると思います。 yield from asyncio.sleep(0.1)を待っている間に、シグナルハンドラはSIGTERMをキャッチし、そのプロセスでtask.cancel()を呼び出します。

スローされた質問:これは_while True:_ループ内でCancelledErrorをトリガーすべきではありませんか?(そうではないが、それが私が理解する方法です 「cancel()を呼び出すと、ラップされたコルーチンにCancelledErrorがスローされます」 )。

最終的にloop.stop()が呼び出され、yield from asyncio.sleep(0.1)が結果を返すか、コルーチン全体_listen_to_ipc_channel_layer_を返すのを待たずにループを停止します。

間違っている場合は修正してください。

私がする必要があるのは、プログラムがyield from asyncio.sleep(0.1)が結果を返すのを待つことだけだと思いますand/orコルーチンがwhileループを抜けて終了します。

私は多くのことを混乱させると信じています。警告なしでイベントループを適切に閉じる方法を理解できるように、これらのことをまっすぐに理解してください。

20
Daniel

問題は、タスクをキャンセルした直後にループを閉じることに起因します。 cancel()docs state として

「これにより、CancelledErrorが、イベントループを通じて次のサイクルでラップされたコルーチンにスローされます。」

次のコードスニペットをご覧ください。

_import asyncio
import signal


async def pending_Doom():
    await asyncio.sleep(2)
    print(">> Cancelling tasks now")
    for task in asyncio.Task.all_tasks():
        task.cancel()

    print(">> Done cancelling tasks")
    asyncio.get_event_loop().stop()


def ask_exit():
    for task in asyncio.Task.all_tasks():
        task.cancel()


async def looping_coro():
    print("Executing coroutine")
    while True:
        try:
            await asyncio.sleep(0.25)
        except asyncio.CancelledError:
            print("Got CancelledError")
            break

        print("Done waiting")

    print("Done executing coroutine")
    asyncio.get_event_loop().stop()


def main():
    asyncio.async(pending_Doom())
    asyncio.async(looping_coro())

    loop = asyncio.get_event_loop()
    for sig in (signal.SIGINT, signal.SIGTERM):
        loop.add_signal_handler(sig, ask_exit)

    loop.run_forever()

    # I had to manually remove the handlers to
    # avoid an exception on BaseEventLoop.__del__
    for sig in (signal.SIGINT, signal.SIGTERM):
        loop.remove_signal_handler(sig)


if __name__ == '__main__':
    main()
_

_ask_exit_はタスクをキャンセルしますが、ループをstopしません。次のサイクルでlooping_coro()はそれを停止します。キャンセルした場合の出力は次のとおりです。

_Executing coroutine
Done waiting
Done waiting
Done waiting
Done waiting
^CGot CancelledError
Done executing coroutine
_

_pending_Doom_がループの直後にキャンセルおよび停止する方法に注意してください。 _pending_Doom_コルーチンがスリープから復帰するまで実行させると、同じ警告が表示されます:

_Executing coroutine
Done waiting
Done waiting
Done waiting
Done waiting
Done waiting
Done waiting
Done waiting
>> Cancelling tasks now
>> Done cancelling tasks
Task was destroyed but it is pending!
task: <Task pending coro=<looping_coro() running at canceling_coroutines.py:24> wait_for=<Future cancelled>>
_
15
Yeray Diaz Diaz

問題の意味は、ループがすべてのタスクを完了する時間がないことです。

これにより、イベントループの次のサイクルでCancelledErrorがラップされたコルーチンにスローされるようになります。

アプローチでループの「次のサイクル」を実行する機会はありません。適切に行うには、停止操作を別の非循環コルーチンに移動して、ループに終了の機会を与える必要があります。

2番目に重要なことは、CancelledErrorの引き上げです。

Future.cancel()とは異なり、これはタスクがキャンセルされることを保証するものではありません。例外がキャッチされて処理され、タスクのキャンセルを遅らせたり、キャンセルを完全に防止したりします。タスクは値を返すか、別の例外を発生させることもあります。

このメソッドが呼び出された直後、cancelled()はTrueを返しません(タスクが既にキャンセルされていない場合)。ラップされたコルーチンがCancelledError例外で終了すると、cancel()が呼び出されなかった場合でも、タスクはキャンセル済みとしてマークされます。

したがって、クリーンアップ後、コルーチンはCancelledErrorを発生させてキャンセル済みとしてマークする必要があります。

追加のコルーチンを使用してループを停止することは、循環的ではなく、実行後すぐに実行されるため、問題ではありません。

def main():                                              
    loop = asyncio.get_event_loop()                      
    asyncio.ensure_future(listen_to_ipc_channel_layer()) 

    for sig in (signal.SIGINT, signal.SIGTERM):          
        loop.add_signal_handler(sig, ask_exit)           
    loop.run_forever()                                   
    print("Close")                                       
    loop.close()                                         


@asyncio.coroutine                                       
def listen_to_ipc_channel_layer():                       
    while True:                                          
        try:                                             
            print("Running")                                 
            yield from asyncio.sleep(0.1)                
        except asyncio.CancelledError as e:              
            print("Break it out")                        
            raise e # Raise a proper error


# Stop the loop concurrently           
@asyncio.coroutine                                       
def exit():                                              
    loop = asyncio.get_event_loop()                      
    print("Stop")                                        
    loop.stop()                                          


def ask_exit():                          
    for task in asyncio.Task.all_tasks():
        task.cancel()                    
    asyncio.ensure_future(exit())        


if __name__ == "__main__":               
    main()                               
7
I159