Async/await
async/awaitパターンは、多くのプログラミング言語における構文機能であり、非同期非ブロッキング関数を通常の同期関数と同様の方法で構築できる。それは意味的にコルーチンの概念と関連し、多くの場合は類似した技術を使用して実装される。主に実行時間の長い非同期タスクの完了を待っている間に他のコードを実行する機会の提供を目的とし、通常は promise または同様のデータ構造で表される。 この機能はC# 5.0、VB.NET 11、Python 3.5、Hack、Dart、Kotlin 1.1、Rust 1.39[1]、Nim 0.9.4[2]、ECMAScript (JavaScript) 2017 (ES2017)、C++20にて利用できるほか、Scala[3]などでもいくつかの拡張、ベータ版、および特定の実装において実験的な成果物がある。 例:C#以下のC#メソッド(関数)は、指定されたURIからリソースをダウンロードし、そのリソースの長さを返す。 public static async Task<int> FindPageSize(Uri uri)
{
byte[] data = await new WebClient().DownloadDataTaskAsync(uri);
return data.Length;
}
async/awaitを使用するメソッドは、必要な数の C#の特定のケース、およびこの言語機能を備えた他の多くの言語では、async/awaitパターンは言語のランタイムのコアパーツではなく、コンパイル時にラムダまたは継続を使用して実装される。たとえば、C#コンパイラーは、上記のコードをIL バイトコード形式に変換する前に、次のようなコードに変換する可能性がある。 public static Task<int> FindPageSize(Uri uri)
{
Task<byte[]> data_task = new WebClient().DownloadDataTaskAsync(uri);
Task<int> after_data_task = data_task.ContinueWith((original_task) => {
return original_task.Result.Length;
});
return after_data_task;
}
このため、インターフェイスメソッドがpromiseオブジェクトを返す必要があるが、それ自体が非同期タスクを待機する しかし、この機能の一つの重要な注意点は、コードは従来のブロッキングコードに似ている一方で、コードが実際に非ブロックおよびマルチスレッドであることにより、 var a = state.a;
var data = await new WebClient().DownloadDataTaskAsync(uri);
Debug.Assert(a == state.a); // イベントハンドラーの介入により値が変更される潜在的な問題がある。
return data.Length;
F#の場合2007年のF#リリースには、「非同期ワークフロー」が含まれている[5] 。非同期ワークフローはCE(コンピュテーション式)として記述され、(C#の 次の非同期関数では、非同期ワークフローを使用してURLで示すデータをダウンロードする。 let asyncSumPageSizes (uris: #seq<Uri>) : Async<int> = async {
use httpClient = new HttpClient()
let! pages =
uris
|> Seq.map(httpClient.GetStringAsync >> Async.AwaitTask)
|> Async.Parallel
return pages |> Seq.fold (fun accumulator current -> current.Length + accumulator) 0
}
C#の場合2011年にリリースされたAsync CTPでプロトタイプ版が実装され[6]、2012年のC# 5.0で正式にサポートされた。 C# 7より前のバージョンでは、非同期メソッドは
次の非同期メソッドは、 public async Task<int> SumPageSizesAsync(IList<Uri> uris)
{
int total = 0;
foreach (var uri in uris)
{
this.statusText.Text = string.Format("Found {0} bytes ...", total);
var data = await new WebClient().DownloadDataTaskAsync(uri);
total += data.Length;
}
this.statusText.Text = string.Format("Found {0} bytes total", total);
return total;
}
注意: C#コンパイラの Scalaの場合Scalaの実験的なScala-async拡張機能では、通常のメソッドとは異なるものの、 使い方Scala-asyncでは、 Scala-asyncが非同期のものを含む、異なるさまざまな実装をサポートする計画もある。 Pythonの場合Python 3.5は、async/awaitのサポートを追加した。PEP0492( https://www.python.org/dev/peps/pep-0492/ )を参照のこと。 import asyncio
async def main():
print("hello")
await asyncio.sleep(1)
print("world")
asyncio.run(main())
JavaScriptの場合JavaScriptのawait演算子は、非同期関数内からのみ使用できる。パラメータがpromiseの場合、promiseが解決されると非同期関数の実行が再開される(promiseが拒否されない限り。拒否された場合、通常のJavaScript 例外処理で処理できるエラーがスローされる)。パラメータがpromiseでない場合、パラメータ自体はすぐに返される[8]。 多くのライブラリは、ネイティブJavaScriptプロミスの仕様に一致する限り、awaitでも使用できるpromiseオブジェクトを提供する。ただし、jQueryライブラリのpromiseは、jQuery 3.0まではPromises/A+互換ではなかった[9]。 次に例を示す(この記事[10]から一部改変): async function createNewDoc() {
const response = await db.post({}); // docを送信する
return await db.get(response.id); // idで検索する
}
async function main() {
try {
const doc = await createNewDoc();
console.log(doc);
} catch (err) {
console.log(err);
}
}
main();
Node.jsバージョン8には、標準ライブラリのコールバックベースのメソッドをPromiseとして使用できるようにするユーティリティが含まれている[11]。 C++の場合C++では、awaitが正式にC++20ドラフトにマージされたため、正式なC++20の一部として正式に受理される予定である[12]。ただし、実際のC++キーワードは #include <future>
#include <iostream>
using namespace std;
future<int> add(int a, int b)
{
int c = a + b;
co_return c;
}
future<void> test()
{
int ret = co_await add(1, 2);
cout << "return " << ret << endl;
}
int main()
{
auto fut = test();
fut.wait();
return 0;
}
Cの場合C言語でのawait/asyncの正式なサポートはまだ存在しない。s_taskなどの一部のコルーチンライブラリは、マクロでawait/asyncキーワードをシミュレートする。 #include <stdio.h>
#include "s_task.h"
// タスク用にメモリ定義
int g_stack_main[64 * 1024 / sizeof(int)];
int g_stack0[64 * 1024 / sizeof(int)];
int g_stack1[64 * 1024 / sizeof(int)];
void sub_task(__async__, void* arg) {
int i;
int n = (int)(size_t)arg;
for (i = 0; i < 5; ++i) {
printf("task %d, delay seconds = %d, i = %d\n", n, n, i);
s_task_msleep(__await__, n * 1000);
//s_task_yield(__await__);
}
}
void main_task(__async__, void* arg) {
int i;
// 2つのサブタスクを作成
s_task_create(g_stack0, sizeof(g_stack0), sub_task, (void*)1);
s_task_create(g_stack1, sizeof(g_stack1), sub_task, (void*)2);
for (i = 0; i < 4; ++i) {
printf("task_main arg = %p, i = %d\n", arg, i);
s_task_yield(__await__);
}
// サブタスクの終了を待つ
s_task_join(__await__, g_stack0);
s_task_join(__await__, g_stack1);
}
int main(int argc, char* argv) {
s_task_init_system();
// メインタスクを作成
s_task_create(g_stack_main, sizeof(g_stack_main), main_task, (void*)(size_t)argc);
s_task_join(__await__, g_stack_main);
printf("all task is over\n");
return 0;
}
Perl5の場合Future::AsyncAwaitモジュールは、2018年9月のPerl財団助成金の対象であった[13]。 Rustの場合2019年11月7日、async/awaitがRustの安定バージョンで利用可能になった[14]。Rustにおいて、非同期関数はFutureトレイトを実装する値を返すプレーンな関数に脱糖(desugar)される。現在は、それらは有限状態マシンで実装される。 // futuresクレートを使用するために、クレートのCargo.tomlの依存関係セクションに`futures = "0.3.0"`を定義する必要あり。
extern crate futures; // 現在、`std`ライブラリに executor は存在しない。
// 以下のように脱糖(desugar)される。
// `fn async_add_one(num: u32) -> impl Future<Output = u32>`
async fn async_add_one(num: u32) -> u32 {
num + 1
}
async fn example_task() {
let number = async_add_one(5).await;
println!("5 + 1 = {}", number);
}
fn main() {
// futureは、作成された時点ではタスクは開始されない。
let future = example_task(5);
// JavaScriptと異なり、futureはポーリングされて初めて開始される。
futures::executor::block_on(future);
}
メリットと批判Async/awaitパターンをサポートする言語の大きな利点は、非同期の非ブロッキングコードを最小限のオーバーヘッドで記述でき、従来の同期ブロックコードとほとんど同じように見えることである。特にawaitは、メッセージパッシングプログラムで非同期コードを記述する最良の方法であると主張されてきた。特に、ブロッキングコードに近いため、読みやすさと定型コードの最小量が利点として挙げられた[15] 。その結果、async/awaitを使用すると、ほとんどのプログラマーがプログラムについて推論しやすくなり、awaitは、それを必要とするアプリケーションでより優れた、より堅牢なノンブロッキングコードを促進する傾向がある。このようなアプリケーションは、グラフィカルユーザインタフェースを提供するプログラムから、ゲームや金融アプリケーションなど、非常にスケーラブルなステートフルなサーバー側プログラムまでさまざまである。 awaitが批判されるときには、awaitは周囲のコードも非同期になる傾向があることがしばしば指摘される。一方で、このコードの伝染性(「ゾンビウイルス」と比較されることもある)はあらゆる種類の非同期プログラミングに固有であると主張されてきたため、この点に関してはawaitだけに特有のものではない[7]。 関連項目脚注
|