asyncio での開発
****************

非同期プログラミングは伝統的な "同期的" プログラミングとは異なります。

このページはよくある間違いや落とし穴を列挙し、それらを回避する方法を説
明します。


デバッグモード
==============

asyncio はデフォルトで本運用モードで実行されます。いっぽう、開発を容易
にするために asyncio は "デバッグモード" を持っています。

asyncio のデバッグモードを有効化する方法はいくつかあります:

* "PYTHONASYNCIODEBUG" 環境変数の値を "1" に設定する。

* Python 開発モード を使う。

* "asyncio.run()" 実行時に "debug=True" を設定する。

* "loop.set_debug()" を呼び出す。

デバッグモードを有効化することに加え、以下も検討してください:

* asyncio ロガー のログレベルを "logging.DEBUG" に設定します。例えばア
  プリケーションの起動時に以下を実行します:

     logging.basicConfig(level=logging.DEBUG)

* "warnings" モジュールが "ResourceWarning" 警告を表示するように設定し
  ます。やり方のひとつは "-W" "default" コマンドラインオプションを使う
  ことです。

デバッグモードが有効化されたときの動作:

* asyncio は 待ち受け処理 (await) を伴わないコルーチン がないかをチェ
  ックし、それらを記録します; これにより "待ち受け忘れ" の落とし穴には
  まる可能性を軽減します。

* スレッドセーフでない asyncio APIs の多く ("loop.call_soon()" や
  "loop.call_at()" など) は、誤ったスレッドから呼び出されたときに例外
  を送出します。

* I/O セレクタが I/O 処理を実行する時間が長すぎる場合、その実行時間が
  記録されます。

* 実行時間が100ミリ秒を超えるコールバックは記録されます。 "遅い" の判
  断基準となる実行時間の最小値は "loop.slow_callback_duration" 属性で
  設定できます。


並行処理とマルチスレッド処理
============================

イベントループはスレッド（典型的にはメインスレッド）内で動作し、すべて
のコールバックとタスクをそのスレッド内で実行します。ひとつのタスクがイ
ベントループ内で実行される間、他のタスクを同じスレッド内で実行すること
はできません。タスクが "await" 式を実行すると、実行中のタスクはサスペ
ンドされ、イベントループは次のタスクを実行します。

別の OS スレッドからのコールバック (*callback*) をスケジュールする場合
、  "loop.call_soon_threadsafe()" メソッドを使ってください。例:

   loop.call_soon_threadsafe(callback, *args)

ほぼ全ての非同期オブジェクトはスレッドセーフではありませんが、タスクや
コールバックの外側で非同期オブジェクトを使うコードが存在しない限り、そ
れが問題にはなることはほとんどありません。もしそのような目的で低レベル
の asyncio API を呼び出すようなコードを書く必要がある場合、
"loop.call_soon_threadsafe()" メソッドを使ってください。例:

   loop.call_soon_threadsafe(fut.cancel)

別の OS スレッドからコルーチンオブジェクトをスケジュールする場合は、
"run_coroutine_threadsafe()" メソッドを使ってください。
"run_coroutine_threadsafe()" は結果にアクセスするための
"concurrent.futures.Future"  オブジェクトを返します:

   async def coro_func():
        return await asyncio.sleep(1, 42)

   # Later in another OS thread:

   future = asyncio.run_coroutine_threadsafe(coro_func(), loop)
   # Wait for the result:
   result = future.result()

シグナルの処理やサブプロセスの実行を行うには、イベントループはメインス
レッド内で実行しなければなりません。

The "loop.run_in_executor()" メソッドを
"concurrent.futures.ThreadPoolExecutor" とともに使用することで、イベン
トループの OS スレッドをブロックすることなく、別の OS スレッド内でブロ
ッキングコードを実行することができます。

There is currently no way to schedule coroutines or callbacks directly
from a different process (such as one started with "multiprocessing").
The イベントループのメソッド section lists APIs that can read from
pipes and watch file descriptors without blocking the event loop. In
addition, asyncio's Subprocess APIs provide a way to start a process
and communicate with it from the event loop. Lastly, the
aforementioned "loop.run_in_executor()" method can also be used with a
"concurrent.futures.ProcessPoolExecutor" to execute code in a
different process.


ブロッキングコードの実行
========================

ブロッキングコード (CPU バウンドなコード) を直接呼び出すべきではありま
せん。たとえば、 CPU 負荷の高い関数を1秒実行したとすると、並行に処理さ
れている全ての非同期タスクと I/O 処理は1秒遅れる可能性があります。

エグゼキューターを使用することにより、イベントループの OS スレッドをブ
ロックすることなく、別のスレッドや別のプロセス上でタスクを実行すること
ができます。詳しくは "loop.run_in_executor()" メソッドを参照してくださ
い。


ログ記録
========

asyncio は "logging" モジュールを利用し、 全てのログ記録は ""asyncio""
ロガーを通じて行われます。

デフォルトのログレベルは "logging.INFO" ですが、これは簡単に調節できま
す:

   logging.getLogger("asyncio").setLevel(logging.WARNING)

Network logging can block the event loop. It is recommended to use a
separate thread for handling logs or use non-blocking IO. For example,
see ブロックする handler を扱う.


待ち受け処理を伴わないコルーチンの検出
======================================

コルーチンが呼び出されただけで、待ち受け処理がない場合 (たとえば
"await coro()" のかわりに "coro()" と書いてしまった場合) 、またはコル
ーチンが "asyncio.create_task()" を使わずにスケジュールされた場合、
asyncio は "RuntimeWarning" 警告を送出します:

   import asyncio

   async def test():
       print("never scheduled")

   async def main():
       test()

   asyncio.run(main())

出力:

   test.py:7: RuntimeWarning: coroutine 'test' was never awaited
     test()

デバッグモードの出力:

   test.py:7: RuntimeWarning: coroutine 'test' was never awaited
   Coroutine created at (most recent call last)
     File "../t.py", line 9, in <module>
       asyncio.run(main(), debug=True)

     < .. >

     File "../t.py", line 7, in main
       test()
     test()

通常の修正方法はコルーチンを待ち受ける (await) か、
"asyncio.create_task()" 関数を呼び出すことです:

   async def main():
       await test()


回収されない例外の検出
======================

もし "Future.set_exception()" メソッドが呼び出されても、その Future オ
ブジェクトを待ち受けていなければ、例外は決してユーザーコードまで伝播し
ません。この場合 asyncio は、 Future オブジェクトがガベージコレクショ
ンの対象となったときにログメッセージを送出することがあります。

処理されない例外の例:

   import asyncio

   async def bug():
       raise Exception("not consumed")

   async def main():
       asyncio.create_task(bug())

   asyncio.run(main())

出力:

   Task exception was never retrieved
   future: <Task finished coro=<bug() done, defined at test.py:3>
     exception=Exception('not consumed')>

   Traceback (most recent call last):
     File "test.py", line 4, in bug
       raise Exception("not consumed")
   Exception: not consumed

タスクが生成された箇所を特定するには、 デバッグモードを有効化して トレ
ースバックを取得してください:

   asyncio.run(main(), debug=True)

デバッグモードの出力:

   Task exception was never retrieved
   future: <Task finished coro=<bug() done, defined at test.py:3>
       exception=Exception('not consumed') created at asyncio/tasks.py:321>

   source_traceback: Object created at (most recent call last):
     File "../t.py", line 9, in <module>
       asyncio.run(main(), debug=True)

   < .. >

   Traceback (most recent call last):
     File "../t.py", line 4, in bug
       raise Exception("not consumed")
   Exception: not consumed
