コンピュータプログラミングにおいて、async/awaitパターンは多くのプログラミング言語の構文上の特徴であり、非同期の非ブロッキング 関数を通常の同期関数と同様の方法で構造化することを可能にする。意味的にはコルーチンの概念に関連しており、同様の技術を使用して実装されることが多く、主に、通常promiseまたは同様のデータ構造で表される長時間実行される非同期タスクが完了するのを待つ間にプログラムが他のコードを実行する機会を提供することを意図している。この機能はC#、[1] : 10 C++、Python、F#、Hack、Julia、Dart、Kotlin、Rust、[2] Nim、[3] JavaScript、Swiftに見られる。[4]
歴史
F#は2007年のバージョン2.0で待機ポイントを備えた非同期ワークフローを追加しました。[5]これはC#に追加されたasync/awaitメカニズムに影響を与えました。[6]
Microsoftは、Async CTP(2011年)で初めてasync/awaitを備えたC#のバージョンをリリースしました。その後、C# 5(2012年)で正式にリリースされました。[7] [1] : 10
Haskellの主任開発者であるSimon Marlowは2012年にasyncパッケージを作成しました。[8]
Pythonは2015年のバージョン3.5でasync/awaitのサポートを追加し[9]、 2つの新しいキーワード、asyncおよびを追加しましたawait。
TypeScriptは2015年のバージョン1.7でasync/awaitのサポートを追加しました。[10]
JavaScript では、2017 年にECMAScript 2017 JavaScript エディションの一部として async/await のサポートが追加されました。
Rustは2019年のバージョン1.39.0で、2018年版の言語で導入されたasyncキーワードと後置演算子を使用してasync/awaitのサポートを追加しました。 [11].await
C++ は、
2020 年のバージョン 20で、3 つの新しいキーワードco_return、、とともに async/await のサポートを追加しましたco_await。co_yield
Swiftは2021年のバージョン5.5でasync/awaitのサポートを追加し、2つの新しいキーワードasyncとを追加しました。これは、async/awaitを使用して外部から各アクターへのアクセスを仲介する
キーワード[12]を使用したActorモデルawaitの具体的な実装とともにリリースされました。actor
C#の例
URIからリソースをダウンロードし、リソースの長さを返す 以下のC#関数は、この async/await パターンを使用します。
public async Task < int > FindSizeOfPageAsync ( Uri uri ) { var client = new HttpClient ( ) ; byte [ ] data = await client.GetByteArrayAsync ( uri ) ; return data.Length ; }
- まず、
asyncキーワードはC#にメソッドが非同期であることを示します。つまり、任意の数のawait式を使用でき、その結果をPromiseにバインドします。[1] :165–168 - 戻り値の型 は、C
Task<T># の promise の概念に類似しており、ここでは 型の結果値を持つことが示されていますint。 - このメソッドが呼び出されたときに実行される最初の式は
new HttpClient().GetByteArrayAsync(uri)、[13] : 189–190, 344 [1] : 882 であり、これは を返す別の非同期メソッドです。このメソッドは非同期であるため、戻る前にデータのバッチ全体をダウンロードすることはありません。代わりに、非ブロッキングメカニズム(バックグラウンドスレッドTask<byte[]>など)を使用してダウンロードプロセスを開始し、未解決で拒否されていない をすぐにこの関数に返します。Task<byte[]> awaitにキーワードが付加されるとTask、この関数は直ちに呼び出し元に を返しTask<int>、呼び出し元は必要に応じて他の処理を続行できます。GetByteArrayAsync()ダウンロードが完了すると、Taskダウンロードされたデータで返された が解決されます。これによりコールバックがトリガーされ、FindPageSizeAsync()その値を に割り当てることで の実行が継続されますdata。- 最後に、メソッドは
data.Length配列の長さを示す単純な整数 を返します。コンパイラはこれを、Task以前に返された を解決するものとして再解釈し、メソッドの呼び出し元でその長さの値を使用して何かを行うコールバックをトリガーします。
async/await を使用する関数はawait、必要な数だけ式を使用でき、各式は同じ方法で処理されます (ただし、promise は最初の await に対してのみ呼び出し元に返され、その他のすべての await は内部コールバックを利用します)。関数は、promise オブジェクトを直接保持し、他の処理 (他の非同期タスクの開始を含む) を最初に実行して、結果が必要になるまで promise の待機を遅らせることもできます。promise を使用する関数には、promise 集約メソッドもあり、これにより、プログラムは複数の promise を一度に、または特別なパターン (引数内のすべてのタスクが解決したときに解決する値のない値を返す C# など) で待機できます。 Task.WhenAll()多くの promise タイプには、複数の結果コールバックを設定したり、特に長時間実行されるタスクの進行状況を検査したりするなど、async/await パターンが通常使用するものを超えた追加機能もあります。
Task
C# の場合、およびこの言語機能を備えた他の多くの言語の場合、async/await パターンは言語ランタイムのコア部分ではなく、コンパイル時にラムダまたは継続を使用して実装されます。たとえば、C# コンパイラは、上記のコードを ILバイトコード形式 に変換する前に、次のように変換する可能性があります。
パブリックTask < int > FindSizeOfPageAsync ( Uri uri ) { var client = new HttpClient (); Task < byte [] > dataTask = client . GetByteArrayAsync ( uri ); Task < int > afterDataTask = dataTask . ContinueWith (( originalTask ) => { return originalTask . Result . Length ; }); return afterDataTask ; }
このため、インターフェースメソッドが Promise オブジェクトを返す必要があるが、メソッドawait本体で非同期タスクを待機する必要がない場合は、修飾子も必要なくasync、代わりに Promise オブジェクトを直接返すことができます。たとえば、関数は、何らかの結果値に即座に解決される Promise を提供できる場合があります (C# のTask.FromResult()[13] : 656 など)。または、必要な Promise とまったく同じ別のメソッドの Promise を返すこともできます (オーバーロードに委ねる場合など)。
ただし、この機能の重要な注意点は、コードは従来のブロッキング コードに似ていますが、実際には非ブロッキングであり、潜在的にマルチスレッドであるため、 の対象となる promise がawait解決するのを待機している間に多くの介入イベントが発生する可能性があることです。たとえば、次のコードは、 のないブロッキング モデルでは常に成功しますがawait、 中に介入イベントが発生する可能性がありawait、その結果、共有状態がその下から変更される可能性があります。
var a = state.a ; var client = new HttpClient (); var data = await client.GetByteArrayAsync ( uri ) ; Debug.Assert ( a == state.a ) ; //潜在的に障害が発生します。state.a の値は、潜在的に介入するイベントのハンドラーによって変更された可能性があります。return data.Length ;
実装
F#の場合
2007年、F#はバージョン2.0で非同期ワークフローを追加しました。[14]非同期ワークフローはCE(計算式)として実装されています。特別なコンテキストを指定せずに定義できます(asyncC#のように)。F #の 非同期ワークフローは、キーワードに感嘆符(!)を追加して非同期タスクを開始します。
次の非同期関数は、非同期ワークフローを使用して 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#の場合
2012 年、C# はバージョン 5.0 で async/await パターンを追加しました。Microsoft はこれをタスク ベースの非同期パターン (TAP) と呼んでいます。[15]非同期メソッドは通常void、Task、Task<T>、 、[13] : 35 [16] : 546–547 [1] : 22, 182 ValueTaskまたは のいずれかを返しますValueTask<T>。[13] : 651–652 [1] : 182–184 ユーザー コードは、カスタム非同期メソッド ビルダーを通じて非同期メソッドが返すことができるカスタム型を定義できますが、これは高度でまれなシナリオです。[17]を返す非同期メソッドはイベント ハンドラーvoid用です。同期メソッドが を返すほとんどの場合、より直感的な例外処理を可能にするため、 を返すことが推奨されます。[18]voidTask
を使用するメソッドは、キーワードを使用awaitして宣言する必要がありますasync。 型の戻り値を持つメソッドではTask<T>、 で宣言されたメソッドは、ではなく にasync代入可能な型の return ステートメントを持つ必要があります。コンパイラは、ジェネリックで値をラップします。なしで宣言されたまたはの戻り値を持つメソッドも可能です。
TTask<T>Task<T>awaitTaskTask<T>async
次の非同期メソッドは、 を使用して URL からデータをダウンロードしますawait。このメソッドは、キーワードによる完了を要求する前に各 URI に対してタスクを発行するためawait、最後のリソースが終了するまで待ってから次のリソースの読み込みを開始するのではなく、リソースを同時に読み込むことができます。
パブリック非同期Task < int > SumPageSizesAsync ( IEnumerable < Uri > uris ) { var client = new HttpClient (); int total = 0 ; var loadUriTasks = new List < Task < byte [] >> ();
foreach ( var uri in uris ) { var loadUriTask = client.GetByteArrayAsync ( uri ) ; loadUriTasks.Add ( loadUriTask ) ; }
foreach ( var loadUriTask in loadUriTasks ) { statusText . Text = $"合計 {total} バイトが見つかりました..." ; var resourceAsBytes = await loadUriTask ; total += resourceAsBytes . Length ; }
statusText . Text = $"合計 {total} バイトが見つかりました" ;
合計を返す; }
Pythonの場合
Python 3.5 (2015) [19]では、PEP 492 (Yury Selivanov によって作成および実装) で説明されている async/await のサポートが追加されました。[20]
インポート asyncio
async def main ( ):
print ( " hello " )
await asyncio.sleep ( 1 ) print ( "world" )
asyncio.run ( main ())を実行します。
JavaScriptの場合
JavaScriptのawait演算子は、非同期関数内またはモジュールのトップレベルでのみ使用できます。パラメータがpromiseの場合、promiseが解決されると非同期関数の実行が再開されます(promiseが拒否されない限り、その場合は通常のJavaScript例外処理で処理できるエラーがスローされます)。パラメータがpromiseでない場合は、パラメータ自体がすぐに返されます。[21]
多くのライブラリは、ネイティブJavaScriptのPromiseの仕様に一致する限り、awaitでも使用できるPromiseオブジェクトを提供しています。しかし、jQueryライブラリのPromiseはjQuery 3.0まではPromises/A+と互換性がありませんでした。[22]
以下に例を示します(この[23]の記事から改変したものです)。
async function createNewDoc () { let response = await db . post ({}); // 新しいドキュメントを投稿するreturn db . get ( response . id ); // ID で検索する}
非同期関数main ( ) { try { let doc = await createNewDoc ( ); console.log ( doc ); } catch ( err ) { console.log ( err ); } } main ( ) ;
Node.jsバージョン8には、標準ライブラリのコールバックベースのメソッドをPromiseとして使用できるようにするユーティリティが含まれています。[24]
C++の場合
C++では、await(C++ではco_awaitという名前)がバージョン20に正式に統合されました。[25] await、コルーチン、などのキーワードのサポートはGCCおよびMSVCコンパイラco_awaitで利用できますが、 Clangでは部分的にサポートされています。
std::promise と std::future は待機可能なオブジェクトのように見えますが、コルーチンから返されて co_await を使用して待機するために必要な機構を実装していないことに注意してください。プログラマーは、型を待機するために、戻り値の型に、、、などのいくつかのパブリックメンバー関数を実装する必要がありますawait_ready。await_suspend詳細await_resumeについては、cppreference を参照してください。[26]
#include <iostream> #include "CustomAwaitableTask.h"
名前空間std を使用します。
CustomAwaitableTask < int >を追加します( int a 、int b ) { int c = a + b ; co_return c ; }
CustomAwaitableTask < int > test () { int ret = co_await add ( 1 , 2 ); cout << "return " << ret << endl ; co_return ret ; }
int main () {自動タスク=テスト();
0 を返す; }
C言語
C言語はawait /asyncをサポートしていません。s_task [27]などの一部のコルーチンライブラリは、マクロを使用してキーワード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 ( "すべてのタスクが終了しました\n " ); return 0 ; }
Perl 5の場合
Future::AsyncAwait [28]モジュールは、2018年9月にPerl Foundationの助成金の対象となりました。[29]
Rustで
2019年11月7日、Rustの安定版でasync/awaitがリリースされました。[30] Rustの非同期関数は、 Futureトレイトを実装した値を返す単純な関数にデシュガーされます。現在は有限状態マシンで実装されています。[31]
// クレートのCargo.tomlでは、依存関係セクションに`futures = "0.3.0"`が必要です。
// こうすることで、futuresクレートを使用できます。
extern crate futures ; // 現在、`std` ライブラリにはエグゼキュータがありません。
// これは次のようなものにデシュガーされます
// `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 ( ) ;
// `Future` は、Javascript とは異なり、実際にポーリングしたときにのみ実行されます。
futures :: executor :: block_on ( future ); }
Swiftでは
Swift 5.5(2021)[32]では、 SE-0296に記載されているasync/awaitのサポートが追加されました。[33]
func getNumber () async throws -> Int {
try await Task . sleep (ナノ秒: 1_000_000_000 )
return 42
}
タスク {
let first = try await getNumber ()
let second = try await getNumber ()
print ( first + second )
}
利点と批判
async/awaitパターンは、独自のランタイムを持たない、または制御しない言語の言語設計者にとって特に魅力的です。なぜなら、async/awaitはコンパイラ内のステートマシンへの変換としてのみ実装できるからです。 [34]
支持者は、非同期の非ブロッキング コードは、従来の同期のブロッキング コードとほとんど同じように見える async/await で記述できると主張しています。特に、メッセージ パッシングプログラムで非同期コードを書くには await が最良の方法であると主張されてきました。特に、ブロッキング コードに近いこと、読みやすさ、定型コードが最小限であることなどがawait の利点として挙げられています。[35]その結果、ほとんどのプログラマーが async/await によってプログラムについて推論しやすくなり、await は、それを必要とするアプリケーションでより優れた、より堅牢な非ブロッキング コードを促進する傾向があります。[疑わしい–議論する]
async/await の批評家は、このパターンは周囲のコードも非同期にする傾向があること、そしてその伝染性により言語のライブラリ エコシステムが同期ライブラリと非同期ライブラリおよび API に分割され、この問題が「関数カラーリング」と呼ばれることが多いことを指摘しています。[36]この問題が発生しない async/await の代替手段は「カラーレス」と呼ばれます。カラーレス設計の例としては、Go のgoroutineや Java の仮想スレッドなどがあります。[37]
参照
参考文献
- ^ abcdefg ジョン・スキート (2019 年 3 月 23 日). C# の詳細。マニング。ISBN 978-1617294532。
- ^ 「Rust 1.39.0 を発表」。2019 年 11 月 7 日閲覧。
- ^ 「バージョン 0.9.4 リリース - Nim ブログ」。2020 年 1 月 19 日閲覧。
- ^ 「並行性 — Swift プログラミング言語 (Swift 5.5)」。docs.swift.org。2021年 9 月 28 日閲覧。
- ^ Syme, Don; Petricek, Tomas; Lomov, Dmitry (2011). 「F# 非同期プログラミング モデル」。宣言型言語の実践的側面。コンピュータ サイエンスの講義ノート。第 6539 巻。Springer リンク。pp. 175–189。doi : 10.1007/ 978-3-642-18378-2_15。ISBN 978-3-642-18377-5. 2021年4月29日閲覧。
- ^ 「F# の初期の歴史、HOPL IV」。ACMデジタル ライブラリ。2021 年 4 月 29 日閲覧。
- ^ Hejlsberg, Anders. 「Anders Hejlsberg: Async の紹介 - 非同期プログラミングの簡素化」。Channel 9 MSDN。Microsoft。2021年1月 5 日閲覧。
- ^ 「async: IO 操作を非同期に実行し、その結果を待機します」。Hackage。
- ^ 「Python 3.5 の新機能 — Python 3.9.1 ドキュメント」。docs.python.org。2021年1月 5 日閲覧。
- ^ Gaurav, Seth (2015 年 11 月 30 日). 「TypeScript 1.7 を発表」. TypeScript . Microsoft . 2021 年1 月 5 日閲覧。
- ^ Matsakis, Niko. 「安定した Rust での Async-await! | Rust ブログ」。blog.rust- lang.org。Rust ブログ。2021 年1 月 5 日閲覧。
- ^ 「並行性 - Swift プログラミング言語 (Swift 5.6)」。
- ^ abcde アルバハリ、ジョセフ (2022). C# 10 の概要。オライリー。ISBN 978-1-098-12195-2。
- ^ 「F# 非同期ワークフローの紹介」。2007 年 10 月 10 日。
- ^ 「タスクベースの非同期パターン」。Microsoft 。2020 年9 月 28 日閲覧。
- ^ Price, Mark J. (2022). C# 8.0 および .NET Core 3.0 – 最新のクロスプラットフォーム開発: Visual Studio Code を使用して C#、.NET Core、Entity Framework Core、ASP.NET Core、ML.NET でアプリケーションを構築する. Packt. ISBN 978-1-098-12195-2。
- ^ Tepliakov, Sergey (2018-01-11). 「C# での非同期メソッドの拡張」.開発者サポート. 2022-10-30閲覧。
- ^ Stephen Cleary、Async/Await - 非同期プログラミングのベストプラクティス
- ^ 「Python リリース Python 3.5.0」。
- ^ 「PEP 492 – async および await 構文を使用したコルーチン」。
- ^ 「await - JavaScript (MDN)」。2017年5月2日閲覧。
- ^ 「jQuery Core 3.0 アップグレード ガイド」。2017 年5 月 2 日閲覧。
- ^ 「ES7 で非同期のモンスターを飼いならす」 。2015年11 月 12 日閲覧。
- ^ Foundation、Node.js (2017 年 5 月 30 日)。「Node v8.0.0 (現在) - Node.js」。Node.js。
- ^ 「ISO C++委員会、C++20の設計が機能完了したことを発表」2019年2月25日。
- ^ 「コルーチン(C++20)」。
- ^ 「s_task - C 用の待機可能なコルーチン ライブラリ」。GitHub。
- ^ 「Future::AsyncAwait - 将来のための遅延サブルーチン構文」。
- ^ 「2018年9月の助成金投票 - Perl Foundation」。news.perlfoundation.org 。 2019年3月26日閲覧。
- ^ Matsakis, Niko. 「安定した Rust での async-await!」Rust ブログ. 2019 年11 月 7 日閲覧。
- ^ Oppermann, Philipp. 「Async/Await」 . 2020年10月28日閲覧。
- ^ “アーカイブコピー”. 2022年1月23日時点のオリジナルよりアーカイブ。2021年12月20日閲覧。
{{cite web}}: CS1 maint: アーカイブされたコピーをタイトルとして (リンク) - ^ “SE-0296”. GitHub .
- ^ 「非同期パート 3 - C# コンパイラが非同期関数を実装する方法」。
- ^ 「No Bugs」Hare。メッセージパッシングプログラムで非ブロッキングリターンを処理する8つの方法 CPPCON、2018
- ^ 「あなたの関数は何色ですか?」
- ^ 「仮想スレッド」。
