非同期プログラミングAsynchronous programming

I/O バインドのニーズ (ネットワークからのデータの要求、データベースへのアクセスなど) がある場合、非同期プログラミングを利用することになります。If you have any I/O-bound needs (such as requesting data from a network or accessing a database), you'll want to utilize asynchronous programming. CPU バインドのコードにも、コストのかかる計算の実行など、非同期コードに適したシナリオがあります。You could also have CPU-bound code, such as performing an expensive calculation, which is also a good scenario for writing async code.

C# は言語レベルで非同期プログラミング モデルを備えており、コールバックに苦労したり、非同期処理をサポートするライブラリに従ったりしなくても、非同期コードを簡単に記述できます。C# has a language-level asynchronous programming model which allows for easily writing asynchronous code without having to juggle callbacks or conform to a library which supports asynchrony. C# は、タスク ベースの非同期パターン (TAP) と呼ばれるものに従います。It follows what is known as the Task-based Asynchronous Pattern (TAP).

非同期モデルの基本的な概要Basic Overview of the Asynchronous Model

非同期プログラミングの中心になるのは Task オブジェクトと Task<T> オブジェクトであり、非同期操作をモデル化します。The core of async programming is the Task and Task<T> objects, which model asynchronous operations. これらは、async および await キーワードによってサポートされています。They are supported by the async and await keywords. ほとんどの場合、モデルは非常に単純です。The model is fairly simple in most cases:

I/O バインドのコードでは、async メソッドの内部で Task または Task<T> を返す操作を待機 (await) します。For I/O-bound code, you await an operation which returns a Task or Task<T> inside of an async method.

CPU バインドのコードでは、Task.Run メソッドによってバックグラウンド スレッドで開始された操作を待機 (await) します。For CPU-bound code, you await an operation which is started on a background thread with the Task.Run method.

await キーワードはマジックが行われる場所であり、The await keyword is where the magic happens. await を実行したメソッドの呼び出し元に制御が委譲されます。これによって最終的に、UI は応答できるようになり、サービスは柔軟性を持つようになります。It yields control to the caller of the method that performed await, and it ultimately allows a UI to be responsive or a service to be elastic.

上でリンクされている TAP の記事で説明されているように asyncawait 以外にも非同期コードへのアプローチはありますが、このドキュメントではこれ以降、言語レベルの構造に注目します。There are other ways to approach async code than async and await outlined in the TAP article linked above, but this document will focus on the language-level constructs from this point forward.

I/O バインドの例:Web サービスからのデータのダウンロードI/O-Bound Example: Downloading data from a web service

ボタンがクリックされたら Web サービスからデータをダウンロードする必要がありますが UI スレッドはブロックしたくない、といった場合があります。You may need to download some data from a web service when a button is pressed, but don’t want to block the UI thread. これは、次のように簡単に実行できます。It can be accomplished simply like this:

private readonly HttpClient _httpClient = new HttpClient();

downloadButton.Clicked += async (o, e) =>
{
    // This line will yield control to the UI as the request
    // from the web service is happening.
    //
    // The UI thread is now free to perform other work.
    var stringData = await _httpClient.GetStringAsync(URL);
    DoSomethingWithData(stringData);
};

以上です。And that’s it! コードでは、Task オブジェクトとの対話に煩わされることなく意図すること (データを非同期的にダウンロードする) が表されています。The code expresses the intent (downloading some data asynchronously) without getting bogged down in interacting with Task objects.

CPU バインドの例:ゲームの計算を実行するCPU-bound Example: Performing a Calculation for a Game

ボタンをクリックすると画面上の多くの敵にダメージを与えることができるモバイル ゲームを作っています。Say you're writing a mobile game where pressing a button can inflict damage on many enemies on the screen. ダメージ計算の実行は負荷が大きく、UI スレッドで実行すると計算の実行中はゲームが停止しているように見えます。Performing the damage calculation can be expensive, and doing it on the UI thread would make the game appear to pause as the calculation is performed!

これを処理する最善の方法は、バックグラウンド スレッドを開始し、その中で Task.Run を使って処理を実行して、結果を await することです。The best way to handle this is to start a background thread which does the work using Task.Run, and await its result. このようにすると、処理が行われている間も UI が停止することはありません。This will allow the UI to feel smooth as the work is being done.

private DamageResult CalculateDamageDone()
{
    // Code omitted:
    //
    // Does an expensive calculation and returns
    // the result of that calculation.
}

calculateButton.Clicked += async (o, e) =>
{
    // This line will yield control to the UI while CalculateDamageDone()
    // performs its work.  The UI thread is free to perform other work.
    var damageResult = await Task.Run(() => CalculateDamageDone());
    DisplayDamage(damageResult);
};

以上です。And that's it! このコードでは、ボタンのクリック イベントの意図が明瞭に表されています。バックグラウンド スレッドを手動で管理する必要はなく、ブロッキングが発生しない方法で行われます。This code cleanly expresses the intent of the button's click event, it doesn't require managing a background thread manually, and it does so in a non-blocking way.

内部での処理What happens under the covers

非同期操作には多数の動作要素が関係します。There's a lot of moving pieces where asynchronous operations are concerned. TaskTask<T> の内部処理について詳しくは、「非同期の詳細」をご覧ください。If you're curious about what's happening underneath the covers of Task and Task<T>, checkout the Async in-depth article for more information.

C# 側では、コンパイラはコードをステート マシンに変換します。ステート マシンは、await に達したときの実行の委譲や、バックグラウンド ジョブが終了したときの実行の再開などを追跡します。On the C# side of things, the compiler transforms your code into a state machine which keeps track of things like yielding execution when an await is reached and resuming execution when a background job has finished.

理論的には、これは非同期処理の約束モデルの実装です。For the theoretically-inclined, this is an implementation of the Promise Model of asynchrony.

理解すべき重要事項Key Pieces to Understand

  • 非同期コードは I/O バインドと CPU バインドの両方のコードで使うことができますが、使い方はシナリオにより異なります。Async code can be used for both I/O-bound and CPU-bound code, but differently for each scenario.
  • 非同期コードで使われる Task<T>Task は、バックグラウンドで行われる処理のモデル化に使われる構成要素です。Async code uses Task<T> and Task, which are constructs used to model work being done in the background.
  • キーワード async はメソッドを非同期メソッドに変換し、メソッドの本体で await キーワードを使用できるようにします。The async keyword turns a method into an async method, which allows you to use the await keyword in its body.
  • 適用された await キーワードは、呼び出しメソッドを中断し、待機中のタスクが完了するまで呼び出し元に制御を戻します。When the await keyword is applied, it suspends the calling method and yields control back to its caller until the awaited task is complete.
  • await は、非同期メソッドの中でのみ使用できます。await can only be used inside an async method.

CPU バインドと I/O バインドの処理Recognize CPU-Bound and I/O-Bound Work

このガイドの最初の 2 つの例では、CPU バインドと I/O バインドの処理に対して async および await を使う方法を示しました。The first two examples of this guide showed how you can use async and await for I/O-bound and CPU-bound work. 行う必要のあるジョブが I/O バインドか CPU バインドかを識別できることが重要です。これは、コードのパフォーマンスに大きく影響し、特定の構成の誤った使用につながる可能性があります。It's key that you can identify when a job you need to do is I/O-bound or CPU-bound, because it can greatly affect the performance of your code and could potentially lead to misusing certain constructs.

コードを記述する前に次の 2 点について考える必要があります。Here are two questions you should ask before you write any code:

  1. コードは何か (データベースのデータなど) を "待機" していますか。Will your code be "waiting" for something, such as data from a database?

    答えが "はい" の場合、その処理は I/O バインドです。If your answer is "yes", then your work is I/O-bound.

  2. コードは、非常に負荷の大きい計算を実行しますか。Will your code be performing a very expensive computation?

    答えが "はい" の場合、その処理は CPU バインドです。If you answered "yes", then your work is CPU-bound.

処理が I/O バインドの場合は、asyncawait を使いますが、Task.Run は "使いません"。If the work you have is I/O-bound, use async and await without Task.Run. タスク並列ライブラリは "使わないでください"。You should not use the Task Parallel Library. その理由について詳しくは、「非同期の詳細」をご覧ください。The reason for this is outlined in the Async in Depth article.

処理が CPU バインドであり、応答性が重要な場合は、asyncawait を使い、Task.Run を "使って" 別のスレッドで処理を実行します。If the work you have is CPU-bound and you care about responsiveness, use async and await but spawn the work off on another thread with Task.Run. 処理がコンカレンシーと並列処理に適している場合は、タスク並列ライブラリを使うことも考慮する必要があります。If the work is appropriate for concurrency and parallelism, you should also consider using the Task Parallel Library.

さらに、常にコードの実行を測定する必要があります。Additionally, you should always measure the execution of your code. たとえば、マルチスレッドでのコンテキスト切り替えのオーバーヘッドと比較して、CPU バインドの処理の負荷がそれほど大きくないことがわかる場合があります。For example, you may find yourself in a situation where your CPU-bound work is not costly enough compared with the overhead of context switches when multithreading. すべての選択肢にはトレードオフがあり、状況に合った適切なトレードオフを選ぶ必要があります。Every choice has its tradeoff, and you should pick the correct tradeoff for your situation.

その他の例More Examples

以下では、C# で非同期コードを記述するさまざまな方法がわかる例を示します。The following examples demonstrate various ways you can write async code in C#. 実際に遭遇する可能性があるいくつかの異なるシナリオを使います。They cover a few different scenarios you may come across.

ネットワークからのデータの抽出Extracting Data from a Network

このスニペットでは、www.dotnetfoundation.org にあるホームページから HTML がダウンロードされ、HTML に文字列 ".NET" が出現する回数が数えられます。This snippet downloads the HTML from the homepage at www.dotnetfoundation.org and counts the number of times the string ".NET" occurs in the HTML. ASP.NET MVC を使って定義されている Web コントローラー メソッドが、このタスクを実行して、数を返します。It uses ASP.NET MVC to define a web controller method which performs this task, returning the number.

注意

運用コードで HTML の解析の実行を計画している場合は、正規表現を使用しないでください。If you plan on doing HTML parsing in production code, don't use regular expressions. 代わりに解析ライブラリを使用します。Use a parsing library instead.

private readonly HttpClient _httpClient = new HttpClient();

[HttpGet]
[Route("DotNetCount")]
public async Task<int> GetDotNetCountAsync()
{
    // Suspends GetDotNetCountAsync() to allow the caller (the web server)
    // to accept another request, rather than blocking on this one.
    var html = await _httpClient.GetStringAsync("https://dotnetfoundation.org");

    return Regex.Matches(html, @"\.NET").Count;
}

次に示すのはユニバーサル Windows アプリ用に記述された同じシナリオであり、ボタンがクリックされたら同じタスクを実行します。Here's the same scenario written for a Universal Windows App, which performs the same task when a Button is pressed:

private readonly HttpClient _httpClient = new HttpClient();

private async void SeeTheDotNets_Click(object sender, RoutedEventArgs e)
{
    // Capture the task handle here so we can await the background task later.
    var getDotNetFoundationHtmlTask = _httpClient.GetStringAsync("https://www.dotnetfoundation.org");

    // Any other work on the UI thread can be done here, such as enabling a Progress Bar.
    // This is important to do here, before the "await" call, so that the user
    // sees the progress bar before execution of this method is yielded.
    NetworkProgressBar.IsEnabled = true;
    NetworkProgressBar.Visibility = Visibility.Visible;

    // The await operator suspends SeeTheDotNets_Click, returning control to its caller.
    // This is what allows the app to be responsive and not block the UI thread.
    var html = await getDotNetFoundationHtmlTask;
    int count = Regex.Matches(html, @"\.NET").Count;

    DotNetCountLabel.Text = $"Number of .NETs on dotnetfoundation.org: {count}";

    NetworkProgressBar.IsEnabled = false;
    NetworkProgressBar.Visibility = Visibility.Collapsed;
}

複数タスクの完了の待機Waiting for Multiple Tasks to Complete

複数のデータを同時に取得することが必要になる場合があります。You may find yourself in a situation where you need to retrieve multiple pieces of data concurrently. Task API には 2 つのメソッド Task.WhenAllTask.WhenAny が含まれており、これらを使用して、複数のバックグラウンド ジョブで非ブロッキング待機を実行する非同期コードを記述できます。The Task API contains two methods, Task.WhenAll and Task.WhenAny which allow you to write asynchronous code which performs a non-blocking wait on multiple background jobs.

次の例では、一連の userId に対する User データを取得する方法を示します。This example shows how you might grab User data for a set of userIds.

public async Task<User> GetUserAsync(int userId)
{
    // Code omitted:
    //
    // Given a user Id {userId}, retrieves a User object corresponding
    // to the entry in the database with {userId} as its Id.
}

public static async Task<IEnumerable<User>> GetUsersAsync(IEnumerable<int> userIds)
{
    var getUserTasks = new List<Task<User>>();

    foreach (int userId in userIds)
    {
        getUserTasks.Add(GetUserAsync(userId));
    }

    return await Task.WhenAll(getUserTasks);
}

LINQ を使ってもう少し簡潔に記述する別の方法を次に示します。Here's another way to write this a bit more succinctly, using LINQ:

public async Task<User> GetUserAsync(int userId)
{
    // Code omitted:
    //
    // Given a user Id {userId}, retrieves a User object corresponding
    // to the entry in the database with {userId} as its Id.
}

public static async Task<User[]> GetUsersAsync(IEnumerable<int> userIds)
{
    var getUserTasks = userIds.Select(id => GetUserAsync(id));
    return await Task.WhenAll(getUserTasks);
}

コードは少ないですが、LINQ と非同期コードを併用するときは注意が必要です。Although it's less code, take care when mixing LINQ with asynchronous code. LINQ は遅延実行を使うので、生成されたシーケンスを .ToList() または .ToArray() の呼び出しで強制的に反復処理させない限り、foreach() ループ内で行われた非同期呼び出しはすぐに実行されません。Because LINQ uses deferred (lazy) execution, async calls won't happen immediately as they do in a foreach() loop unless you force the generated sequence to iterate with a call to .ToList() or .ToArray().

重要な情報とアドバイスImportant Info and Advice

非同期プログラミングはそれほど難しくありませんが、留意することで予期しない動作を防ぐことができる細かい事柄がいくつかあります。Although async programming is relatively straightforward, there are some details to keep in mind which can prevent unexpected behavior.

  • async メソッドの本体に await キーワードが含まれないと、何も行われません。async methods need to have an await keyword in their body or they will never yield!

これは、忘れてはならない重要なことです。This is important to keep in mind. awaitasync メソッドの本体で使われていない場合、C# コンパイラは警告を生成しますが、コードは通常のメソッドと同様にコンパイルされて実行されます。If await is not used in the body of an async method, the C# compiler will generate a warning, but the code will compile and run as if it were a normal method. また、非同期メソッドに対して C# コンパイラが生成するステート マシンは何も行わないので、非常に非効率的であることにも注意してください。Note that this would also be incredibly inefficient, as the state machine generated by the C# compiler for the async method would not be accomplishing anything.

  • 記述するすべての非同期メソッド名のサフィックスとして、"Async" を追加する必要がありますYou should add "Async" as the suffix of every async method name you write.

これは、同期メソッドと非同期メソッドの区別をより簡単にするために、.NET で使われる規則です。This is the convention used in .NET to more-easily differentiate synchronous and asynchronous methods. コードによって明示的に呼び出されない一部のメソッド (イベント ハンドラーや Web コントローラー メソッドなど) には必ずしも当てはまらないことに注意してください。Note that certain methods which aren’t explicitly called by your code (such as event handlers or web controller methods) don’t necessarily apply. そのようなメソッドはコードでは明示的に呼び出されないため、明示的な命名はそれほど重要ではありません。Because these are not explicitly called by your code, being explicit about their naming isn’t as important.

  • async void はイベント ハンドラーに対してのみ使う必要があります。async void should only be used for event handlers.

イベントには戻り値の型がないため、async void は非同期イベント ハンドラーの動作を可能にする唯一の方法です (したがって、TaskTask<T> を使うことはできません)。async void is the only way to allow asynchronous event handlers to work because events do not have return types (thus cannot make use of Task and Task<T>). async void のその他の使用はすべて TAP モデルに従わないので、使うのが難しい場合があります。以下はその例です。Any other use of async void does not follow the TAP model and can be challenging to use, such as:

  • async void メソッドでスローされた例外を、そのメソッドの外部でキャッチすることはできません。Exceptions thrown in an async void method can’t be caught outside of that method.

  • async void メソッドをテストするのは非常に困難です。async void methods are very difficult to test.

  • async void メソッドは、呼び出し元がそれを非同期と予期していないと、悪い副作用が発生する可能性があります。async void methods can cause bad side effects if the caller isn’t expecting them to be async.

  • LINQ 式での非同期ラムダの使用は慎重に行う必要がありますTread carefully when using async lambdas in LINQ expressions

LINQ 内のラムダ式は遅延実行を使います。つまり、予期していないときにコードが実行される可能性があります。Lambda expressions in LINQ use deferred execution, meaning code could end up executing at a time when you’re not expecting it to. これにブロッキング タスクを組み込んだ場合、正しく作成されていないと、簡単にデッドロックが発生します。The introduction of blocking tasks into this can easily result in a deadlock if not written correctly. さらに、このように非同期コードを入れ子にすると、コードの実行についての推論も難しくなります。Additionally, the nesting of asynchronous code like this can also make it more difficult to reason about the execution of the code. 非同期と LINQ は強力ですが、併用するときは可能な限り慎重かつ明確にする必要があります。Async and LINQ are powerful, but should be used together as carefully and clearly as possible.

  • タスクを待機するコードは非ブロッキング方式で作成しますWrite code that awaits Tasks in a non-blocking manner

タスクが完了するのを待機するための手段として現在のスレッドをブロックすると、デッドロックおよびコンテキスト スレッドのブロックが発生する可能性があり、非常に複雑なエラー処理が必要になることがあります。Blocking the current thread as a means to wait for a Task to complete can result in deadlocks and blocked context threads, and can require significantly more complex error-handling. 次の表では、非ブロッキング方式でタスクの待機に対処する方法について説明します。The following table provides guidance on how to deal with waiting for Tasks in a non-blocking way:

これを使うUse this... これは使わないInstead of this... 行う処理When wishing to do this
await Task.Wait または Task.ResultTask.Wait or Task.Result バックグラウンド タスクの結果の取得Retrieving the result of a background task
await Task.WhenAny Task.WaitAny 任意のタスクの完了の待機Waiting for any task to complete
await Task.WhenAll Task.WaitAll すべてのタスクの完了の待機Waiting for all tasks to complete
await Task.Delay Thread.Sleep 一定期間の待機Waiting for a period of time
  • ステートフル性の低いコードを記述しますWrite less stateful code

グローバル オブジェクトの状態または特定のメソッドの実行に依存しないでください。Don’t depend on the state of global objects or the execution of certain methods. 代わりに、メソッドの戻り値のみに依存するようにします。Instead, depend only on the return values of methods. なぜでしょうか。Why?

  • コードを理解しやすくなります。Code will be easier to reason about.
  • コードをテストしやすくなります。Code will be easier to test.
  • 非同期コードと同期コードの混在がはるかに簡単になります。Mixing async and synchronous code is far simpler.
  • 一般には、競合状態を完全に回避できます。Race conditions can typically be avoided altogether.
  • 戻り値に依存すると、非同期コードの調整が簡単になります。Depending on return values makes coordinating async code simple.
  • (ボーナス) 依存関係の挿入で問題なく動作します。(Bonus) it works really well with dependency injection.

推奨される目標は、完全またはほぼ完全な参照の透過性をコードで実現することです。A recommended goal is to achieve complete or near-complete Referential Transparency in your code. そうすることで、予測、テスト、保守が非常に容易なコードベースになります。Doing so will result in an extremely predictable, testable, and maintainable codebase.

その他の参照情報Other Resources