Async/await

async/awaitパターンは、多くのプログラミング言語における構文機能であり、非同期非ブロッキング関数を通常の同期関数と同様の方法で構築できる。それは意味的にコルーチンの概念と関連し、多くの場合は類似した技術を使用して実装される。主に実行時間の長い非同期タスクの完了を待っている間に他のコードを実行する機会の提供を目的とし、通常は promise または同様のデータ構造で表される。

この機能はC# 5.0、VB.NET 11、Python 3.5、Hack、Dart、Kotlin 1.1、Rust 1.39、Nim 0.9.4、ECMAScript  2017 、C++20にて利用できるほか、Scalaなどでもいくつかの拡張、ベータ版、および特定の実装において実験的な成果物がある。

1. 例：C#
-------

以下のC#メソッドは、指定されたURIからリソースをダウンロードし、そのリソースの長さを返す。 public static async Task FindPageSize { byte[] data = await new WebClient.DownloadDataTaskAsync; return data.Length; }

まず、asyncキーワードはC#コンパイラーに対してメソッドが非同期であることを示す。つまり、任意の数のawait式を使用でき、結果をpromiseにバインドする。
戻り値の型であるTask は、C#におけるpromiseの類似形であり、ここではint型の結果値を持つことが示されている。
このメソッドが呼び出されたときに最初に実行される式は、new WebClient である。DownloadDataTaskAsyncは、Task<byte[]>を返す別の非同期メソッドである。このメソッドは非同期であるため、戻る前にデータのバッチ全体をダウンロードしない。代わりに、非ブロッキングメカニズムを使用してダウンロードプロセスを開始し、解決も拒否もされていないTask<byte[]>をこのメソッドに対して即座に返す。
Taskにアタッチされたawaitキーワードによって、このメソッドはすぐにTaskを呼び出し元に返し、呼び出し元は必要に応じて他の処理を続行できる。
DownloadDataTaskAsyncがダウンロードを完了すると、そのダウンロードしたデータによって、以前返却していたTaskを解決する。これによりコールバックがトリガーされ、その値をdataに割り当てることでFindPageSizeに実行を継続させる。
最後にメソッドは、配列の長さを示す単純な整数値であるdata.Lengthを返す。コンパイラーは、以前に返却されたTaskを解決するものとしてこれを再解釈し、その長さの値を使って何かをするためにメソッドの呼び出し元においてコールバックをトリガーする。async/awaitを使用するメソッドは、必要な数のawait式を使用でき、それぞれが同じ方法で処理される。関数はpromiseオブジェクトを直接保持し、最初に他の処理を実行して、結果が必要になるまでpromiseの待機を遅らせることもできる。promiseを使用する関数には、複数のpromiseを一度にまたは特定のパターンで待機できるようにするpromise集計メソッドもある: Task.WhenAll は、引数内のすべてのTaskが解決されたときに解決される、値のないTaskを返す。多くのpromiseタイプには、複数の結果コールバックを設定したり、特に長時間実行されるタスクの進行状況を検査したりできるなど、async/awaitパターンが通常使用する機能を超える追加機能がある。

C#の特定のケース、およびこの言語機能を備えた他の多くの言語では、async/awaitパターンは言語のランタイムのコアパーツではなく、コンパイル時にラムダまたは継続を使用して実装される。たとえば、C#コンパイラーは、上記のコードをIL バイトコード形式に変換する前に、次のようなコードに変換する可能性がある。 public static Task FindPageSize { Task<byte[]> data_task = new WebClient.DownloadDataTaskAsync; Task after_data_task = data_task.ContinueWith => { return original_task.Result.Length; }); return after_data_task; } このため、インターフェイスメソッドがpromiseオブジェクトを返す必要があるが、それ自体が非同期タスクを待機するawaitに本文での待機を必要としない場合、async修飾子も必要なく、代わりにpromiseオブジェクトを直接返すことができる。たとえば、関数はいくつかの結果値にすぐに解決されるプロミスを提供できる場合があり、または単純に、必要な正確なプロミスである別のメソッドのプロミスを返す場合もある。

しかし、この機能の一つの重要な注意点は、コードは従来のブロッキングコードに似ている一方で、コードが実際に非ブロックおよびマルチスレッドであることにより、awaitの付いた目標のプロミスが解決するのを待っている間に、多くの介在事象が発生する可能性があることを意味する。たとえば、次のコードは、awaitなしで常にブロッキングモデルで成功するが、await中にイベントが発生する可能性があるため、その下から共有状態が変更されていることがわかる。

var a = state.a; var data = await new WebClient.DownloadDataTaskAsync; Debug.Assert; // イベントハンドラーの介入により値が変更される潜在的な問題がある。 return data.Length;

2. F#の場合
--------

2007年のF#リリースには、「非同期ワークフロー」が含まれている 。非同期ワークフローはCEとして記述され、特別なコンテキストを指定することなく定義できる。非同期ワークフローを開始するには、キーワードに感嘆符を付加する。

次の非同期関数では、非同期ワークフローを使用してURLで示すデータをダウンロードする。 let asyncSumPageSizes  : Async = async { use httpClient = new HttpClient let! pages = uris |> Seq.map |> Async.Parallel return pages |> Seq.fold  0 }

3. C#の場合
--------

2011年にリリースされたAsync CTPでプロトタイプ版が実装され、2012年のC# 5.0で正式にサポートされた。

C# 7より前のバージョンでは、非同期メソッドはvoid、Task、またはTaskを返すことが要求される。これはC# 7で拡張され、ValueTaskなどの他の特定の型が含まれるようになった。voidを返す非同期メソッドは、イベントハンドラーを対象としている。同期メソッドがvoidを返すような大抵のケースでは、より直感的な例外処理を可能にするため、代わりにTaskを返すことが推奨される。

awaitを使用するメソッドは、asyncキーワードを付けて宣言する必要がある。Task型の戻り値を持つメソッドでは、async宣言されたメソッドには、TaskではなくTに割り当て可能な型のreturn文が必要である。コンパイラは値をTaskジェネリックでラップする。asyncなしで宣言されたTaskまたはTask戻り値の型を持つメソッドをawaitすることもできる。

次の非同期メソッドは、awaitを使用してURLからデータをダウンロードする。 public async Task SumPageSizesAsync { int total = 0; foreach  { this.statusText.Text = string.Format; var data = await new WebClient.DownloadDataTaskAsync; total += data.Length; } this.statusText.Text = string.Format; return total; }

注意: C#コンパイラのawait対応正式版リリース時から、出力されるコードはステートマシンによる実行効率性の高いコードを生成するように実装されている。そのため、冒頭で示した、promiseをTask.ContinueWithによって継続させる疑似コードの議論は、現実のC#コンパイラには当てはまらない。また、ステートマシンは、コンパイラに完全にハードコーディングされているわけではなく、AsyncStateMachineAttribute属性を使用して、ライブラリ提供者が独自のステートマシンを提供できる拡張ポイントを有している。ValueTaskなどの、より軽量なpromiseと対応するステートマシンは、この機能を使用して実装されている。

4. Scalaの場合
-----------

Scalaの実験的なScala-async拡張機能では、通常のメソッドとは異なるものの、awaitは「メソッド」である。さらに、メソッドを非同期としてマークする必要があるC# 5.0とは異なり、Scala-asyncでは、コードの「ブロック」は非同期の「呼び出し」で囲まれる。

4.1. 使い方
--------

Scala-asyncでは、asyncは実際には、Scalaマクロを使用して実装される。これにより、コンパイラは異なるコードを発行し、有限状態マシン実装を生成する。

Scala-asyncが非同期のものを含む、異なるさまざまな実装をサポートする計画もある。

5. Pythonの場合
------------

Python 3.5は、async/awaitのサポートを追加した。PEP0492を参照のこと。

import asyncio

async def main: print await asyncio.sleep print

asyncio.run)<WtXmlEndTag />

6. JavaScriptの場合
----------------

JavaScriptのawait演算子は、非同期関数内からのみ使用できる。パラメータがpromiseの場合、promiseが解決されると非同期関数の実行が再開される。パラメータがpromiseでない場合、パラメータ自体はすぐに返される。

多くのライブラリは、ネイティブJavaScriptプロミスの仕様に一致する限り、awaitでも使用できるpromiseオブジェクトを提供する。ただし、jQueryライブラリのpromiseは、jQuery 3.0まではPromises/A+互換ではなかった。

次に例を示す: async function createNewDoc { const response = await db.post; // docを送信する return await db.get; // idで検索する }

async function main { try { const doc = await createNewDoc; console.log; } catch  { console.log; } } main;<WtXmlEndTag />Node.jsバージョン8には、標準ライブラリのコールバックベースのメソッドをPromiseとして使用できるようにするユーティリティが含まれている。

7. C++の場合
---------

C++では、awaitが正式にC++20ドラフトにマージされたため、正式なC++20の一部として正式に受理される予定である。ただし、実際のC++キーワードはawaitではなくco_awaitという名前になった。また、MSVCコンパイラとClangコンパイラは、少なくとも何らかの形式のco_awaitをすでにサポートしている。

include
include using namespace std;

future add { int c = a + b; co_return c; }

future test { int ret = co_await add; cout << "return " << ret << endl; }

int main { auto fut = test; fut.wait;

return 0; }<WtXmlEndTag />

8. Cの場合
-------

C言語でのawait/asyncの正式なサポートはまだ存在しない。[1]などの一部のコルーチンライブラリは、マクロでawait/asyncキーワードをシミュレートする。

include
include "s_task.h"// タスク用にメモリ定義 int g_stack_main[64 * 1024 / sizeof]; int g_stack0[64 * 1024 / sizeof]; int g_stack1[64 * 1024 / sizeof];

void sub_task { int i; int n = arg; for  { printf; s_task_msleep; //s_task_yield; } }

void main_task { int i;

// 2つのサブタスクを作成 s_task_create, sub_task, 1); s_task_create, sub_task, 2);

for  { printf; s_task_yield; }

// サブタスクの終了を待つ s_task_join; s_task_join; }

int main {

s_task_init_system;

// メインタスクを作成 s_task_create, main_task, argc); s_task_join; printf; return 0; }<WtXmlEndTag />

9. Perl5の場合
-----------

[2]モジュールは、2018年9月のPerl財団助成金の対象であった。

10. Rustの場合
-----------

2019年11月7日、async/awaitがRustの安定バージョンで利用可能になった。Rustにおいて、非同期関数はFutureトレイトを実装する値を返すプレーンな関数に脱糖される。現在は、それらは有限状態マシンで実装される。

// futuresクレートを使用するために、クレートのCargo.tomlの依存関係セクションに`futures = "0.3.0"`を定義する必要あり。

extern crate futures; // 現在、`std`ライブラリに executor は存在しない。

// 以下のように脱糖される。 // `fn async_add_one -> impl Future` async fn async_add_one -> u32 { num + 1 }

async fn example_task { let number = async_add_one.await; println!; }

fn main { // futureは、作成された時点ではタスクは開始されない。 let future = example_task;

// JavaScriptと異なり、futureはポーリングされて初めて開始される。 futures::executor::block_on; }<WtXmlEndTag />

11. メリットと批判
-----------

Async/awaitパターンをサポートする言語の大きな利点は、非同期の非ブロッキングコードを最小限のオーバーヘッドで記述でき、従来の同期ブロックコードとほとんど同じように見えることである。特にawaitは、メッセージパッシングプログラムで非同期コードを記述する最良の方法であると主張されてきた。特に、ブロッキングコードに近いため、読みやすさと定型コードの最小量が利点として挙げられた 。その結果、async/awaitを使用すると、ほとんどのプログラマーがプログラムについて推論しやすくなり、awaitは、それを必要とするアプリケーションでより優れた、より堅牢なノンブロッキングコードを促進する傾向がある。このようなアプリケーションは、グラフィカルユーザインタフェースを提供するプログラムから、ゲームや金融アプリケーションなど、非常にスケーラブルなステートフルなサーバー側プログラムまでさまざまである。

awaitが批判されるときには、awaitは周囲のコードも非同期になる傾向があることがしばしば指摘される。一方で、このコードの伝染性はあらゆる種類の非同期プログラミングに固有であると主張されてきたため、この点に関してはawaitだけに特有のものではない。

12. 関連項目
--------

コルーチン
継続渡しスタイル

13. 脚注
------