myesn

myEsn2E9

hi
github

C#: タスク非同期プログラミングモデル(Task asynchronous programming model)

非同期プログラミングを使用することで、パフォーマンスのボトルネックを回避し、アプリケーション全体の応答速度を向上させることができます。しかし、従来の非同期アプリケーションを作成する技術は非常に複雑であり、記述、デバッグ、保守が難しくなることがあります。

C# は、.NET ランタイムの非同期サポートを活用する簡素化された方法、すなわち非同期プログラミングをサポートしています。コンパイラは、開発者が以前に行わなければならなかった厄介な作業を完了し、アプリケーションは同期コードに似た論理構造を保持します。したがって、非同期プログラミングのすべての利点を得るために、わずかな作業を行うだけで済みます。

この記事では、非同期プログラミングを使用するタイミングと方法について概説し、詳細情報やサンプルへのリンクを含んでいます

非同期による応答能力の向上#

非同期は、ネットワークアクセスなどのブロックされる可能性のあるアクティビティにとって非常に重要です。ネットワークリソースへのアクセスは、時には非常に遅いまたは遅延することがあります。このようなアクティビティが同期プロセスでブロックされると、アプリケーション全体が待機しなければなりません。一方、非同期プロセスでは、アプリケーションはブロックされる可能性のあるタスクが完了するまで、ネットワークリソースに依存しない他の作業を続行できます

下の表は、非同期プログラミングが応答性を改善する典型的な領域を示しています。非同期プログラミングをサポートする API が、.NET および Windows ランタイムからリストされています。
image

非同期は、UI スレッドにアクセスするアプリケーションにとって特に重要です。なぜなら、UI に関連するすべてのアクティビティは通常、1 つのスレッドを共有するからです。もし同期アプリケーション内のプロセスがブロックされると、すべてのプロセスがブロックされます。アプリケーションは応答を停止し、失敗したと考えるかもしれませんが、実際にはただ待機しているだけです。

非同期メソッドを使用することで、アプリケーションは UI 操作に応答し続けます。たとえば、ウィンドウのサイズを変更したり、ウィンドウを最小化したり、完了を待たずにアプリケーションを閉じたりできます。

非同期操作を設計する際、非同期メソッドはオプションリストに自動転送に相当するオプションを追加します。つまり、開発者は従来の非同期プログラミングのすべての利点を得るために、少ない作業量を投入するだけで済みます。

非同期メソッドは簡単に書ける(Async methods are easy to write)#

C# のasyncおよびawaitキーワードを使用することが非同期プログラミングの核心です。これらの 2 つのキーワードを使用することで、.NET Framework、.NET Core、または Windows ランタイム内のリソースを使用して、同期メソッドを作成するのと同じくらい簡単に非同期メソッドを作成できます。async キーワードで定義された非同期メソッドは async メソッドと呼ばれます。
以下の例は、async メソッドを示しています。
完全な Windows Presentation Foundation(WPF)サンプルは、C# での非同期プログラミングからダウンロードできます。

public async Task<int> GetUrlContentLengthAsync()
{
    var client = new HttpClient();

    Task<string> getStringTask =
        client.GetStringAsync("https://learn.microsoft.com/dotnet");

    DoIndependentWork();

    string contents = await getStringTask;

    return contents.Length;
}

void DoIndependentWork()
{
    Console.WriteLine("Working...");
}

前の例からいくつかの実践的な方法を学ぶことができます。まず、メソッドシグネチャにはasync修飾子が含まれています。戻り値の型はTask<int>です(詳細なオプションについては「戻り値の型」セクションを参照してください)。メソッド名は Async で終わります。メソッド本体では、GetStringAsyncTask<string>を返します。これは、タスクが完了するのを待っている間に文字列(内容)を取得できることを意味します。Task を待機する前に、GetStringAsyncが返す文字列に依存しない作業を実行できます。

await演算子に特に注意してください:

  • GetUrlContentLengthAsyncgetStringTaskが完了するまで実行を続けることができません。
  • 同時に、制御はGetUrlContentLengthAsyncの呼び出し元に戻ります。
  • getStringTaskが完了すると、ここで制御が復元されます。
  • その後、await演算子はgetStringTaskから文字列結果を取得します。

return 文は整数結果を指定します。GetUrlContentLengthAsyncを待機している任意のメソッドは、長さの値を取得します。

もしGetUrlContentLengthAsyncGetStringAsyncを呼び出して完了を待つ間に他に作業がない場合、次の単一の文でコードを簡素化できます

string contents = await client.GetStringAsync("https://learn.microsoft.com/dotnet");

以下の特徴は、前の例の非同期メソッドを要約しています:

  • メソッドシグネチャにはasync修飾子が含まれています
  • 非同期メソッドの名前は通常「Async」で終わります
  • 戻り値の型は次のいずれかの型である可能性があります
    • メソッドに return 文があり、オペランドの型がTResultの場合、Taskを返します。

    • メソッドに return 文がないか、オペランドのない return 文がある場合、Taskを返します。

    • 非同期イベントハンドラーを作成する場合、戻り値はvoidです

    • GetAwaiterメソッドを持つ他の任意の型。

      詳細については、戻り値の型とパラメーターセクションを参照してください。

  • メソッドには通常、少なくとも 1 つのawait式が含まれ、これは待機している非同期操作が完了するまでメソッドが続行できないポイントを示します。同時に、メソッドは一時停止し、制御を呼び出し元に返します。この記事の次のセクションでは、一時停止ポイントで何が起こるかを説明します。

非同期メソッドでは、提供されたキーワードと型を使用して何をするかを示し、コンパイラが残りの作業を担当します。これは、制御が一時停止状態の await ポイントに戻るときに発生することを追跡することを含みます。従来の非同期コードでは、ループや例外処理などの一般的なプロセスが処理しにくい場合があります。しかし、非同期メソッドでは、同期ソリューションのようにこれらの要素を書くことができ、問題は解決されます。

旧版.NET Framework の非同期性について詳しくは、TPL と従来の.NET Framework 非同期プログラミングを参照してください。

非同期メソッドの動作(What happens in an async method)#

非同期プログラミングで最も重要なのは、制御フローが 1 つのメソッドから別のメソッドにどのように移動するかを理解することです。以下の図は、このプロセスを案内します:

呼び出し元が非同期メソッドを呼び出すと、図の数字は以下のステップに対応します:

  1. 呼び出し元はGetUrlContentLengthAsync非同期メソッドを呼び出し、待機します。

  2. GetUrlContentLengthAsyncHttpClientインスタンスを作成し、GetStringAsync非同期メソッドを呼び出して、ウェブサイトの内容を文字列としてダウンロードします。

  3. GetStringAsync内でいくつかの進行状況が一時停止します。ウェブサイトのダウンロードや他のブロッキングアクティビティを待つ必要があるかもしれません。リソースをブロックしないために、GetStringAsyncはその呼び出し元であるGetUrlContentLengthAsyncに制御を返します
    GetStringAsyncTaskを返し、TResultは文字列であり、GetUrlContentLengthAsyncはタスクをgetStringTask変数に割り当てます。このタスクは、GetStringAsyncの呼び出しが進行中であり、作業が完了したときに実際の文字列値を生成することを約束します。

  4. getStringTaskをまだ await していないため、GetUrlContentLengthAsyncGetStringAsyncの最終結果に依存せずに他の作業を続行できます。これらの作業(他の作業を続行する)は、同期メソッドDoIndependentWorkを呼び出すことで表されます。

  5. DoIndependentWorkは同期メソッドであり、作業が完了すると呼び出し元に戻ります。

  6. GetUrlContentLengthAsyncは、getStringTaskの結果が出る前に完了する必要がある作業がなくなりました。次に、GetUrlContentLengthAsyncはダウンロードされた文字列の長さを計算して返したいと考えていますが、その文字列を取得する前にその値を計算することはできません。
    したがって、GetUrlContentLengthAsyncは await 演算子を使用して進行を一時停止し、GetUrlContentLengthAsyncを呼び出すメソッドに制御を返します。GetUrlContentLengthAsyncは呼び出し元にTask<int>を返します。このタスクは、ダウンロードされた文字列の長さという整数結果を生成することを約束します。

    呼び出し元内部では、処理パターンが続行されます。GetUrlContentLengthAsyncの結果を待つ前に、呼び出し元はGetUrlContentLengthAsyncの結果に依存しない他の作業を実行するか、呼び出し元はすぐに待機するかもしれません。呼び出し元はGetUrlContentLengthAsyncを待機しており、GetUrlContentLengthAsyncGetStringAsyncを待機しています。

  7. GetStringAsyncが完了し、文字列結果を生成します。返された文字列結果は、あなたが期待しているものとは異なります。(3 ステップ目で、このメソッドはすでにタスクを返していることを思い出してください)。代わりに、文字列結果はメソッドの完了を表すタスクgetStringTaskに格納されます。await 演算子はgetStringTaskから結果を取得します。代入文は取得した結果をcontents変数に代入します。

  8. GetUrlContentLengthAsyncが文字列結果を取得すると、そのメソッドは文字列の長さを計算できます。次に、GetUrlContentLengthAsyncも作業を完了し、待機イベントハンドラーは実行を再開できます。
    この記事の最後に提供される完全なサンプルでは、イベントハンドラーが長さの値を取得して印刷することを確認できます。非同期プログラミングに初めて触れる場合は、同期と非同期の動作の違いについて考えるために 1 分間時間を取ってください。同期メソッドは作業が完了したときに戻ります(ステップ 5)、しかし非同期メソッドは作業が一時停止したときにタスク値を返します(ステップ 3 と 6)。非同期メソッドが最終的に作業を完了すると、タスクは完了としてマークされ、結果(存在する場合)はタスクに格納されます。

API 非同期メソッド(API async methods)#

.NET Framework 4.5 + および.NET Core には、非同期プログラミングをサポートする多くのメソッドが含まれており、メンバー名の後に「Async」という接尾辞が付いており、戻り値の型がTaskまたはTaskであることが特徴です。たとえば、System.IO.Streamクラスには、CopyToAsyncReadAsync、およびWriteAsyncなどのメソッドがあり、同期メソッドCopyToRead、およびWriteもあります。

Windows ランタイムにも、Windows アプリケーションでasyncおよびawaitを使用できる多くのメソッドが含まれています。詳細については、UWP 開発のスレッドと非同期プログラミングおよび非同期プログラミング(Windows ストアアプリ)、およびC# または Visual Basic で非同期 API を呼び出すクイックスタート(古いバージョンの Windows ランタイムを使用している場合)を参照してください。

スレッド(Thread)#

非同期メソッドは非ブロッキング操作を実現するために設計されています。待機中のタスクが実行されているとき、**await式は現在のスレッドをブロックしません **。代わりに、式はメソッドの残りの部分を続行するようにマークし、制御を非同期メソッドの呼び出し元に返します。

**asyncおよびawaitキーワードは追加のスレッドを作成しません。非同期メソッドはマルチスレッドを必要とせず、独自のスレッドで実行されません **。このメソッドは現在の同期コンテキスト内で実行され、メソッドがアクティブなときにのみスレッド上の時間を使用します。CPU バウンドの作業をバックグラウンドスレッドに移動するにはTask.Runを使用できますが、バックグラウンドスレッドは結果の可用性を待つプロセスには役立ちません

既存のソリューションと比較して、ほぼすべてのケースで async ベースの非同期プログラミング方式が好まれます。特に I/O バウンド操作において、非同期メソッドはBackgroundWorkerクラスよりも優れており、コードがシンプルで競合条件を防ぐ必要がありません。Task.Runメソッドと組み合わせて使用する場合、非同期プログラミングは CPU バウンド操作に対してもBackgroundWorkerクラスよりも優れています。なぜなら、非同期プログラミングはコードの実行の調整の詳細をTask.Runに移し、スレッドプールの作業から分離するからです。

async および await#

async修飾子を使用してメソッドを非同期メソッドとして指定すると、次の 2 つの機能が有効になります:

  • マークされた async メソッドは、awaitを使用して一時停止ポイントを指定できます。await演算子は、待機している非同期プロセスが完了するまで、非同期メソッドはその一時停止ポイント以降を続行できないことをコンパイラに伝えます。この間、制御は非同期メソッドの呼び出し元に戻ります。

    await式で一時停止している非同期メソッドは、メソッドが終了することはなく、finally ブロックも実行されません。

  • マークされた async メソッド自体は、それを呼び出すメソッドによって待機されることができます。

async メソッドには通常、1 つ以上のawait演算子が含まれますが、await式がない場合、コンパイラはエラーを報告しません(警告を発するだけです)。async メソッドがawait演算子を使用して一時停止ポイントをマークしていない場合、そのメソッドは async 修飾子があっても同期メソッドのように実行されます。コンパイラはそのようなメソッドに警告を発し、このようなコードは非常に非効率的であるため、避けることを強く推奨します。

asyncおよびawaitはコンテキストキーワードです。詳細情報とサンプルについては、以下のトピックを参照してください:

戻り値の型とパラメーター(Return types and parameters)#

非同期メソッドでは、通常、TaskまたはTaskを返します。非同期メソッド内では、await演算子は別の非同期メソッド呼び出しから返された Task として機能します。

このメソッドに指定されたTResult型のオペランドを持つreturn文が含まれている場合、Taskを戻り値の型として指定します。

このメソッドに return 文がないか、オペランドのない return 文が含まれている場合、Taskを戻り値の型として使用します。

他の戻り値の型を指定することもできますが、その型にはGetAwaiterメソッドが含まれている必要があります。ValueTaskはこのような型の一例であり、System.Threading.Tasks.Extension NuGet パッケージで提供されています。

以下の例は、TaskまたはTaskを返すメソッドを宣言し、呼び出す方法を示しています:

async Task<int> GetTaskOfTResultAsync()
{
    int hours = 0;
    await Task.Delay(0);

    return hours;
}

Task<int> returnedTaskTResult = GetTaskOfTResultAsync();
int intResult = await returnedTaskTResult;
// 単一行
// int intResult = await GetTaskOfTResultAsync();

async Task GetTaskAsync()
{
    await Task.Delay(0);
    // return文は必要ありません
}

Task returnedTask = GetTaskAsync();
await returnedTask;
// 単一行
await GetTaskAsync();

返される各Taskは進行中の作業を表しますTaskは非同期プロセスの状態に関する情報をカプセル化し、最終的にはそのプロセスからの最終結果またはそのプロセスが引き起こした例外(成功しなかった場合)を含みます。

非同期メソッドもvoid戻り値の型を持つことができます。この戻り値の型は、戻り値が void である必要があるイベントハンドラー(event handler)を定義するために主に使用されます。非同期イベントハンドラーは通常、非同期プログラムの起点として機能します

void戻り値の型を持つ非同期メソッドは待機できず、呼び出し元はそのメソッドがスローした例外をキャッチすることができません

非同期メソッドはinrefまたはoutパラメーターを宣言することはできませんが、そのようなパラメーターを持つメソッドを呼び出すことはできます。同様に、非同期メソッドは参照を介して戻り値を返すことはできませんが、ref 戻り値を持つメソッドを呼び出すことはできます。

詳細情報とサンプルについては、非同期戻り値の型(C#)を参照してください。

Windows ランタイムプログラミングにおける非同期 API は、次のいずれかの戻り値の型を持ちます(Task に類似):

命名規則(Naming convention)#

慣例として、一般的な待機可能な型(例えばTaskTask<T>ValueTaskValueTask<T>)を返すメソッドは「Async」で終わるべきです。非同期操作を開始するが待機可能な型を返さないメソッドは、「Async」で終わる名前を付けるべきではありませんが、「Begin」、「Start」または他の動詞で始めて、このメソッドが結果を返さないか、操作結果をスローしないことを示すことができます。

イベント(event)、基底クラス(base class)またはインターフェース契約(interface contract)が異なる名前を使用することを推奨する場合、その規約(「Async」で終わる)を無視することができます。たとえば、OnButtonClickのような一般的なイベントハンドラーの名前を変更すべきではありません。

関連記事(Visual Studio)#

image

See also#

参考#

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。