PYTHON MEBY

PythonでFuture・Taskを使う(asyncio, Python 3.5以降)

この記事では、Python 3.5以降で導入された`asyncio`における`Future`と`Task`の使い方を解説します。非同期プログラミングにおいて、これらのオブジェクトがどのように役立つのか、具体的な例を通して理解を深めます。

目次

Futureオブジェクト

`Future`は、非同期操作の結果を保持するオブジェクトです。`asyncio.Future`を使用して作成し、`result()`メソッドで結果を取得します。`done()`メソッドで操作が完了したかどうかを確認できます。

import asyncio

async def my_coroutine():
    await asyncio.sleep(1)
    return 'Hello from coroutine!'

async def main():
    future = asyncio.Future()
    asyncio.create_task(my_coroutine()).add_done_callback(lambda f: future.set_result(f.result()))
    result = await future
    print(result)

await main()

上記の例では、`my_coroutine`を`Task`として実行し、その結果を`Future`オブジェクトに設定しています。`await future`で`Future`が完了するまで待ち、結果を取得します。

Taskオブジェクト

`Task`は、`asyncio.create_task()`で`coroutine`を実行して作成されます。`Future`オブジェクトと似ていますが、`coroutine`の実行を管理する役割を担います。`Task`オブジェクト自体も`Future`を継承しているため、`result()`や`done()`メソッドも使用できます。

import asyncio

async def my_coroutine():
    await asyncio.sleep(1)
    return 'Hello from coroutine!'

async def main():
    task = asyncio.create_task(my_coroutine())
    result = await task
    print(result) 

await main()

この例では、`create_task()`で`my_coroutine`を実行し、`Task`オブジェクトを取得。`await task`で完了を待って結果を取得しています。

FutureとTaskの違い

簡単に言うと、`Future`は非同期操作の結果を表すオブジェクト、`Task`は`Future`を管理し、`coroutine`の実行を制御するオブジェクトです。`Task`は`Future`のサブクラスであり、`Future`の機能をすべて継承しています。通常、`asyncio.create_task()`を使用して`Task`を作成し、直接`Future`を作成する必要はほとんどありません。

  • `Future`は結果を保持する
  • `Task`は`coroutine`の実行を管理する
  • `Task`は`Future`のサブクラス

実践例:複数の非同期操作を同時に行う

複数の非同期操作を同時に行うには、複数の`Task`を作成し、`asyncio.gather()`を使って結果を待ちます。

import asyncio

async def fetch_data(url):
    await asyncio.sleep(1)  # シミュレーション
    return f'Data from {url}'

async def main():
    urls = ['url1', 'url2', 'url3']
    tasks = [asyncio.create_task(fetch_data(url)) for url in urls]
    results = await asyncio.gather(*tasks)
    print(results)

await main()

この例では、3つのURLからデータを取得する非同期操作を同時に実行し、`asyncio.gather()`で結果をリストとして取得しています。

エラーハンドリング

`Task`または`Future`が例外を発生させた場合、`result()`メソッドは例外を再発生させます。`try...except`ブロックで例外をキャッチすることができます。

import asyncio

async def my_coroutine():
    await asyncio.sleep(1)
    raise Exception('Something went wrong!')

async def main():
    task = asyncio.create_task(my_coroutine())
    try:
        result = await task
        print(result)
    except Exception as e:
        print(f'An error occurred: {e}')

await main()

この例では、`my_coroutine`が例外を発生させますが、`try...except`ブロックで適切に処理されています。

関連記事