コンピュータプログラミングにおいて、async/awaitパターンは多くのプログラミング言語に共通する構文上の特徴であり、非同期かつノンブロッキングな関数を、通常の同期関数と同様の方法で構造化することを可能にする。
これは意味的にコルーチンの概念と関連しており、同様の手法を使用して実装されることが多く、主に、長時間実行される非同期タスク(通常はプロミスや同様のデータ構造で表される)の完了を待っている間に、プログラムが他のコードを実行する機会を提供することを目的としています。この機能は、 C#、[ 1 ] : 10 C++、Python、F#、Hack、Julia、Dart、Kotlin、Rust、[ 2 ] Nim、[ 3 ] JavaScript、およびSwiftに見られます。[ 4 ]
F# は 2007 年のバージョン 2.0 で await ポイントを使用した非同期ワークフローを追加しました。[ 5 ]これは C# に追加された async/await メカニズムに影響を与えました。[ 6 ]
Haskellのリード開発者であるサイモン・マーロウは、 2010年にasyncパッケージを作成しました。[ 7 ]
Microsoftは、async/awaitを搭載したC#のバージョンをAsync CTP(2011年)で初めてリリースしました。その後、C# 5(2012年)で正式にリリースされました。[ 8 ] [ 1 ]: 10
Pythonは2015 年のバージョン 3.5 で async/await のサポートを追加し[ 9 ]、2 つの新しいキーワードasyncを追加しました。await
TypeScriptは2015年のバージョン1.7でasync/awaitのサポートを追加しました。[ 10 ]
JavaScriptは、 ECMAScript 2017 JavaScript版の一部として、2017年にasync/awaitのサポートを追加しました。
Rust は、2018 年版で導入されたasyncキーワードと後置演算子を使用して、2019 年のバージョン 1.39.0 で async/await のサポートを追加しました。 [ 11 ].await
C++ は 2020 年にバージョン 20 で async/await のサポートを追加し、3co_returnつの新しいキーワードが追加されましたco_await。co_yield
Swift は2021 年にバージョン 5.5で async/await のサポートを追加しasync、 2 つの新しいキーワードを追加しました。これは、外部から各アクターへのアクセスを仲介するために async/await を使用するキーワード[ 12 ]を使用したActor モデルawaitの具体的な実装と同時にリリースされました。actor
以下のC #関数は、 URIからリソースをダウンロードし、そのリソースの長さを返しますが、この async/await パターンを使用しています。
using System ; using System.Net.Http ; using System.Threading.Tasks ;public async Task < int > FindSizeOfPageAsync ( Uri uri ) { HttpClient client = new (); byte [] data = await client . GetByteArrayAsync ( uri ); return data . Length ; }asyncキーワードは、メソッドが非同期であることをC#に示します。つまり、任意の数のawait式を使用でき、結果をプロミスにバインドします。[ 1 ] : 165–168System.Threading.Tasks.Task<T>C# の Promise の概念に相当し、ここでは の結果値が 型であることを示していますint。( を返す場合void、System.Threading.Tasks.Taskクラスは型パラメーターを持たないことに注意してください。)newHttpClient().GetByteArrayAsync(uri)Task<byte[]>Task<byte[]>awaitキーワードが に付加されている場合Task、この関数はすぐに を呼び出し元に返します。呼び出し元は必要に応じて他の処理を続行できます。Task<int>GetByteArrayAsync()ダウンロードが完了すると、Taskダウンロードしたデータで返されたを解決します。これによりコールバックがトリガーされ、FindPageSizeAsync()その値をに割り当てることで実行が続行されますdata。data.Length単純な整数を返します。コンパイラはこれを、以前に返された値を解決するものとして再解釈しTask、メソッドの呼び出し元でコールバックをトリガーして、その長さの値を使って何らかの処理を実行します。async/await を使用する関数はawait、必要なだけ式を使用でき、それぞれが同じように処理されます (ただし、プロミスは最初の await に対してのみ呼び出し元に返され、他のすべての await は内部コールバックを利用します)。関数は、プロミス オブジェクトを直接保持して、他の処理 (他の非同期タスクの開始を含む) を先に実行し、結果が必要になるまでプロミスの待機を遅らせることもできます。プロミスを持つ関数には、プログラムが複数のプロミスを一度に、または特別なパターン (C# のTask.WhenAll(), [ 1 ] : 174–175 [ 13 ] : 664–665など、引数内のすべてのタスクが解決されたときに解決される値のない値を返すTask) で待機できるプロミス集約メソッドもあります。多くのプロミス型には、複数の結果コールバックを設定したり、特に長時間実行されるタスクの進行状況を検査したりするなど、async/await パターンが通常使用する機能を超える追加機能もあります。
C# の場合、そしてこの言語機能を持つ他の多くの言語では、async/await パターンは言語のランタイムの中核部分ではなく、コンパイル時にラムダ式または継続式で実装されます。たとえば、C# コンパイラは上記のコードを ILバイトコード形式に変換する前に、おそらく次のようなコードに変換するでしょう。
using System ; using System.Net.Http ; using System.Threading.Tasks ;public Task <int> FindSizeOfPageAsync ( Uri uri ) { HttpClient client = new ( ) ; 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 を返す場合 (オーバーロードに処理を委譲する場合など) があります。
ただし、この機能の重要な注意点として、コードは従来のブロッキングコードに似ていますが、実際にはノンブロッキングであり、マルチスレッド化される可能性があります。つまり、によってターゲットとされるプロミスがawait解決されるのを待っている間に、多くのイベントが発生する可能性があります。たとえば、次のコードは、を使用しないブロッキングモデルでは常に成功しますがawait、の処理中にイベントが発生する可能性がありawait、共有状態が意図せず変更される可能性があります。
using System ; using System.Diagnostics ; using System.Net.Http ;string name = state.name ; HttpClient client = new ( ) ; byte [ ] data = await client.GetByteArrayAsync ( uri ) ;// state.a の値が、介入する可能性のあるイベントのハンドラによって変更された可能性があるため、潜在的な失敗が発生します。Debug.Assert ( name == state.name ) ;return data.Length ;C言語はawait /asyncをサポートしていません。s_task [ 14 ]などの一部のコルーチンライブラリは、マクロを使用してawait/asyncキーワードをシミュレートします。
#include <stdio.h> #include "s_task.h"constexpr int STACK_SIZE = 64 * 1024 / sizeof ( int );// タスク用のスタックメモリを定義しますint g_stack_main [ STACK_SIZE ]; int g_stack0 [ STACK_SIZE ]; int g_stack1 [ STACK_SIZE ];void sub_task ( __async__ , void * arg ) { int n = ( int )( size_t ) arg ; for ( int 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 ) { // 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 ( int 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 ; }C++20より前にはに相当するものはなくawait、C++ は非同期プログラミングのためのユーティリティがはるかに限られていました。特に、C++11では、先物とプロミスのモデルを提供するstd::promise<T>とが導入され、 は関数を非同期に (別のスレッドで実行する可能性もある) 実行してオブジェクトを返すために使用され、具体的には 型の関数を受け取りを返すとともに、ポリシーを指定できるようにしました。[ 15 ]std::future<T>std::async()std::futureFfuture<invoke_result_t<decay_t<F>, decay_t<Args>...>>std::launch
import std ;using std :: future ; using std :: launch ; using std :: vector ;// CPU負荷の高い処理vector < double > applyFilter ( const vector < double >& image ) { // ... }int main ( int argc , char * argv []) { // 大量のデータvector < double > imagePixels ( 5'000'000 ); std :: iota ( imagePixels . begin (), imagePixels . end (), 1.0 );future < vector < double >> fut = std :: async ( launch :: async , applyFilter , imagePixels );vector <double> processedImage = fut.get ( ) ; }C++20 では、コルーチンの実行を中断する演算子であるco_await(コルーチン内でのコンテキストを強調するために「coco_return 」という接頭辞が付けられています)が導入されました。 は、コルーチンを終了して値を返す と、co_yieldコルーチンを中断して呼び出し元に値を返すとともに使用されます。 [ 16 ] [ 17 ]
ジェネレータクラスはC ++23std::generator<Ref, V, Alloc>で導入され、タスククラスはC++26で導入されました。[ 18 ]コルーチン内ではタスクは実行されますが、コルーチン外では、ブロックしてを返す を使用して呼び出すことができます。[ 19 ]std::execution::task<T, Env>co_awaitstd::this_thread::sync_wait()optional<tuple<Ts...>>
型が待機できるようにするには、戻り値の型に対して、などのパブリックメンバ関数を実装する必要があります。およびco_awaitは、これらのいずれも実装していません。await_ready()await_suspend()await_resume()std::promise<T>std::future<T>
import std ;using std :: optional ; using std :: tuple ; using std :: execution :: task ; using namespace std :: literals ;task < void > simulateWork () { std :: println ( "作業を実行中..." ); std :: this_thread :: sleep_for ( 500 ms ); std :: println ( "作業完了!" ); }task < int > add ( int a , int b ) noexcept { co_await simulateWork (); co_return a + b ; }task < int > test () { int ret = co_await add ( 1 , 2 ); std :: println ( "Return {}" , ret ); co_return ret ; }int main ( int argc , char * argv []) { optional < tuple < int >> result = std :: this_thread :: sync_wait ( test ()); std :: println ( "Result: {}" , std :: get < 0 > ( result ). value_or ( std :: make_tuple ( -1 )));// 空のタプルに、正常完了を確認するための番兵識別子を割り当てます。optional < tuple <>> result = std :: this_thread :: sync_wait ( simulateWork ()); if ( result . has_value ()) { std :: println ( "作業が正常に完了しました。" ); } else { std :: println ( "作業が失敗しました!" ); }return 0 ; }2012 年、C# はバージョン 5.0 で async/await パターンを追加しました。これは Microsoft がタスクベースの非同期パターン (TAP) と呼んでいます。[ 20 ]非同期メソッドは通常void、 、System.Threading.Tasks.Task(「voidタスク」)、System.Threading.Tasks.Task<T>、 、[ 13 ] : 35 [ 21 ] : 546–547 [ 1 ] : 22、182System.Threading.Tasks.ValueTask (「void値タスク」)、または を返しますSystem.Threading.Tasks.ValueTask<T>。[ 13 ] : 651–652 [ 1 ] : 182–184ユーザー コードは、カスタム非同期メソッド ビルダーを使用して、非同期メソッドが返すカスタム型を定義できますが、これは高度でまれなシナリオです。[ 22 ]を返す非同期メソッドはイベント ハンドラーvoid用です。同期メソッドが を返すほとんどの場合、代わりに を返すことが推奨されます。これは、より直感的な例外処理を可能にするためです ( C# では は無効であることに注意してください)。 [ 23 ]voidTaskTask<void>
を使用するメソッドは、キーワードawaitで宣言する必要がありますasync。 型の戻り値を持つメソッドではTask<T>、 で宣言されたメソッドは、の代わりに に代入可能な型のreturn ステートメントasyncを持つ必要があります。コンパイラは値をジェネリックでラップします。で宣言されていない、または戻り値の型を持つメソッドを にすることも可能です。TTask<T>Task<T>awaitTaskTask<T>async
以下の非同期メソッドは、を使用してURLからデータをダウンロードしますawait。このメソッドは、キーワードによる完了を要求する前に各URIに対してタスクを発行するためawait、最後のリソースの読み込みが完了するのを待ってから次のリソースの読み込みを開始するのではなく、リソースを同時に読み込むことができます。
名前空間Wikipedia.Examples ;using System ; using System.Collections.Generic ; using System.Net.Http ; using System.Threading.Tasks ;public class Example { public async Task < int > SumPageSizesAsync ( IEnumerable < Uri > uris ) { HttpClient client = new (); int total = 0 ; List < Task < byte [] >> loadUriTasks = new ();foreach ( Uri uri in uris ) { byte [] loadUriTask = client . GetByteArrayAsync ( uri ); loadUriTasks . Add ( loadUriTask ); }foreach ( Task < byte [] >> loadUriTask in loadUriTasks ) { statusText . Text = $"Found {total} bytes ..." ; byte [] resourceAsBytes = await loadUriTask ; total += resourceAsBytes . Length ; }statusText.Text = $"合計{total}バイトが見つかりました" ;return total ; }static async Task Main ( string [] args ) { List < Uri > uris = new { new Uri ( "https://en.wikipedia.org" ), new Uri ( "https://www.microsoft.com" ), new Uri ( "https://www.github.com" ) }int totalBytes = await SumPageSizesAsync ( uris ); Console . WriteLine ( $"ダウンロードされた合計バイト数: {totalbytes}" ); } }2007年、F#はバージョン2.0で非同期ワークフローを追加しました。[ 24 ]非同期ワークフローはCE(計算式async)として実装されています。C #のように特別なコンテキストを指定せずに定義できます。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 }Javaには非同期処理asyncや非同期処理のキーワードは存在しませんが、Java 8で導入されたクラスなどのパッケージawaitを使用してエミュレートできます。非同期プログラミングは、Java 21で仮想スレッドと構造化タスクスコープが導入されたことでさらに改善されました。java.util.concurrentjava.util.concurrent.CompletableFuture<T>
パッケージorg.wikipedia.examples ;import java.util.concurrent.CompletableFuture ; import java.util.concurrent.ExecutionException ; import java.util.concurrent.ExecutorService ; import java.util.concurrent.Executors ; import java.util.concurrent.Future ; import java.util.concurrent.StructuredTaskScope ;public class AsyncExample { public String fetchData () { // 時間のかかる操作 (ネットワーク要求、データベースクエリなど) をシミュレートしますtry { Thread . sleep ( 2000 ); } catch ( InterruptedException e ) { Thread . currentThread (). interrupt (); } return "リモートソースからのデータ" ; }public CompletableFuture < String > fetchDataAsync () { return CompletableFuture . supplyAsync (() -> fetchData ()); }public static void main ( String [] args ) throws ExecutionException , InterruptedException { AsyncExample example = new AsyncExample ();// CompletableFuture ( Java 8) を使用System.out.println ( "非同期操作を開始します..." );CompletableFuture <String> future = example.fetchDataAsync ( ) ;System.out.println ( "他の作業を実行中... " ) ;//結果を待つ('await'と同様)String result = future.get ( ) ; System.out.printf ( "受信結果: %s%n " , result ) ;//仮想スレッドの使用 (Java 21): ExecutorService extor = Executors.newVirtualThreadPerTaskExecutor ( ); System.out.println ( "仮想スレッド操作を開始しています... " ) ;Future <String> fut = extor 。 submit (() -> { return example . fetchData (); });System.out.println ( "他の作業を実行中... " ) ;String result = future.get ( ); System.out.printf ( "受信結果: %s% n " , result ) ; extor.shutdown ( ) ;// StructuredTaskScope (Java 21) を使用try ( StructuredTaskScope . ShutdownOnFailure scope = new StructuredTaskScope . ShutdownOnFailure ()) { StructuredTaskScope . Subtask < String > dataTask = scope . fork ( example :: fetchData ); System . out . println ( "他の作業を実行中..." );scope.join (); // すべてのタスクが完了するまで待機scope.throwIfFailed ( ) ; //例外が発生した場合は伝播するString result = future.get ( ); System.out.printf ( "受信結果: % s%n" , result ) ; } } }JavaScript (および TypeScript) の await 演算子は、非同期関数内またはモジュールの最上位レベルでのみ使用できます。パラメータがPromiseの場合、Promise が解決されると非同期関数の実行が再開されます (Promise が拒否された場合は、通常の JavaScript例外処理で処理できるエラーがスローされます)。パラメータが Promise でない場合は、パラメータ自体がすぐに返されます。[ 25 ]
多くのライブラリは、ネイティブJavaScriptのpromiseの仕様に合致する限り、awaitでも使用できるpromiseオブジェクトを提供しています。しかし、jQueryライブラリのpromiseは、jQuery 3.0までPromises/A+と互換性がありませんでした。[ 26 ]
以下に例を示します(この[ 27 ]記事から改変)。
interface DBResponse { id : string ; rev? : string ; ok? : boolean ; }interface Document { _id : string ; _rev? : string ; [ key : string ] : any ; }interface Database { post ( doc : object ) : Promise < DBResponse > ; get ( id : string ) : Promise < Document > ; }declare const db : Database ;async function createNewDoc ( ) : Promise <Document> { const response : DBResponse = await db.post ( { } ) ; const doc : Document = await db.get ( response.id ) ; return doc ; }async function main () : Promise <void> { try { const doc : Document = await createNewDoc (); console.log ( doc ); } catch ( err : Error ) { console.error ( "ドキュメントの作成または取得中にエラーが発生しました:" , err ); } }主要();Node.jsバージョン 8 には、標準ライブラリのコールバックベースのメソッドをプロミスとして使用できるようにするユーティリティが含まれています。[ 28 ]
Kotlin は、true /キーワードではなく、async()(from kotlinx.coroutines.CoroutineScope) およびawait()(from ) メソッドを使用してコルーチンを使用します。[ 29 ] [ 30 ]kotlinx.coroutines.Deferred<T>asyncawait
例えば、並列実行のための以下のような用途asyncとawait()方法があります。
import kotlinx.coroutines.* import kotlin.system.measureTimeMillissuspend fun fetchFirstData (): String { delay ( 1000 ) // ネットワーク呼び出しをシミュレートreturn "サーバーAからのデータ" }suspend fun fetchSecondData (): String { delay ( 1000 ) // ネットワーク呼び出しをシミュレートreturn "サーバーBからのデータ" }suspend fun main () = coroutineScope { val time = measureTimeMillis { // バックグラウンドで両方のネットワーク要求を同時に開始しますval deferredOne : Deferred < String > = async { fetchFirstData () } val deferredTwo : Deferred < String > = async { fetchSecondData () }//両方の結果が完了するまで待機しますval resultOne = deferredOne.await ( ) val resultTwo = deferredTwo.await ( )println ( "結合結果: $ resultOne & $ resultTwo " ) } println ( "完了時間: $ time ms" ) }Future::AsyncAwait [ 31 ]モジュールは、2018 年 9 月に Perl Foundation の助成金の対象となりました。[ 32 ]
Python 3.5 (2015) [ 33 ]では、PEP 492 ( Yury Selivanovによって作成および実装) で説明されている async/await のサポートが追加されました。[ 34 ]
import asyncioasync def main () -> None : print ( "hello" ) await asyncio . sleep ( 1 ) print ( "world" )if __name__ == "__main__" : asyncio.run ( main ( ) )2019年11月7日、async/awaitがRustの安定版でリリースされました。[ 35 ] Rustのasync関数は、トレイトを実装する値を返す通常の関数にデシュガーされます。現在、それらは有限状態機械std::future::Futureで実装されています。[ 36 ]
asyncは、式が を返すことを示す指定子でありstd::future::Future、(スレッドをブロックするのではなく)[ 37 ]です。一方、awaitは、( とともに使用される)演算子であり、結果が準備できるまで実行を中断します。[ 38 ].awaitstd::future::Future
// クレートの Cargo.toml の dependencies セクションに `futures = "0.3.0"` を追加する必要があります。// これにより、futures クレートを使用できます。extern crate futures ; // 現在、`std` ライブラリにはエグゼキュータがありません。use std :: future :: Future ;// これは次のようなコードに展開されます// `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 ();// JavaScriptとは異なり、`Future`は実際にポーリングしたときにのみ実行されます。futures :: executor :: block_on ( future ); }Swift 5.5 (2021) [ 39 ]では、SE-0296 [ 40 ]で説明されているように、async/await のサポートが追加されました。
func getNumber ( ) async throws - > Int { try await Task.sleep ( nanoseconds : 1_000_000_000 ) return 42 }タスク{ let first = try await getNumber () let second = try await getNumber () print ( first + second ) }async/await パターンは、独自のランタイムを持たない、または制御できない言語の言語設計者にとって特に魅力的です。なぜなら、async/await はコンパイラでステートマシンへの変換としてのみ実装できるからです。 [ 41 ]
支持者たちは、非同期で非ブロッキングなコードは、従来の同期的なブロッキングコードとほとんど変わらないように見える async/await を使って記述できると主張している。特に、メッセージパッシングプログラムでは await が非同期コードを記述する最良の方法であると主張されてきた。特に、ブロッキングコードに近いこと、可読性、定型コードの最小化がawait の利点として挙げられている。[ 42 ]
async/await の批判者たちは、このパターンが周囲のコードも非同期化させる傾向があること、そしてその伝染性によって言語のライブラリのエコシステムが同期ライブラリと非同期ライブラリおよび API に分かれてしまうことを指摘している。この問題はしばしば「関数の色付け」と呼ばれている。[ 43 ]この問題に悩まされない async/await の代替案は「カラーレス」と呼ばれる。カラーレス設計の例としては、Go のゴルーチンや Java の仮想スレッドなどがある。[ 44 ]
{{cite web}}: CS1 maint: タイトルとしてアーカイブされたコピー (リンク)