web-dev-qa-db-ja.com

どのように非同期待機を使用する必要がありますか?

Async awaitの使用方法を調べていましたが、複数のメソッドが相互に呼び出している場合は、あまり理解できません。常にawaitを使用する必要がありますか、それとも実際に結果を使用する準備ができたときにのみawaitを使用する必要がありますか?

たとえば、次のようにする必要があります。

async Task<string[]> FooAsync()
{
    var info = await Func1();
    return info.split('.');
}

async Task<string> Func1()
{
    return await Func2();
}

async Task<string> Func2()
{
    return await tcpClient.ReadStringAsync();
}

またはこのように:

async Task<string[]> FooAsync()
{
    var info = await Func1();
    return info.split('.');
}

Task<string> Func1()
{
    return Func2();
}

Task<string> Func2()
{
    return tcpClient.ReadStringAsync();
}

例1のように、すべてのメソッドで常にawaitを使用する必要がありますか?
Or
例2のように、結果の使用を開始するときに、一番上のメソッドでのみawaitを使用する必要がありますか?

40
Vincent

awaitを呼び出すたびに、variablesをバンドルするコードの塊が作成され、synchronizationコンテキストがキャプチャされ(該当する場合)、IAsyncStateMachineへの継続が作成されます。

基本的に、Taskなしでasyncを返すkeywordを使用すると、run-timeの効率が小さくなり、[〜#〜] cil [〜 #〜]。NETAsync機能にもすでに多くの最適化が施されていることに注意してください。また、(そして重要なことに)Taskステートメントでusingを返すと、おそらくAlready Disposed Exceptionがスローされることに注意してください。

[〜#〜] cil [〜#〜]と配管の違いをここで比較できます

したがって、メソッドがTaskを転送するだけで、そこから何も必要としない場合は、asyncキーワードを削除してTaskを直接返すだけで済みます。

さらに、forwardingだけではなく、分岐が発生する場合もあります。これは、 Task.FromResult および Task.CompletedTask メソッドで発生する可能性のあるロジックに対処するために役立ちます。つまり、result(there then then)またはreturnTaskつまりcompleted(それぞれ)を指定したい場合。

最後に、例外を処理する場合、非同期および待機パターンには微妙な違いがあります。 Taskを返す場合は、 Task.FromException<T> は、Taskメソッドが通常行うように、返されたasyncに例外をポップします。

無意味な例

public Task<int> DoSomethingAsync(int someValue)
{
   try
   {
      if (someValue == 1)
         return Task.FromResult(3); // Return a completed task

      return MyAsyncMethod(); // Return a task
   }
   catch (Exception e)
   {
      return Task.FromException<int>(e); // Place exception on the task
   }
}

つまり、何が起こっているのかよくわからない場合は、awaitにしてください。オーバーヘッドは最小限になります。ただし、タスクの結果完了したタスクタスクの例外、または単に-を返す方法の字幕を理解している場合転送。自分自身をいくらか保存する[〜#〜] cil [〜#〜]し、asyncキーワードを削除してタスクを直接返し、IAsyncStateMachineをバイパスすることで、コードのパフォーマンスを少し向上させることができます。


この頃、Stack Overflowのユーザーと著者Stephen ClearyとMr. Parallel Stephen Toubを調べます。 非同期および待機パターンだけに特化したブログや本がたくさんあります。すべての落とし穴、エチケットのコーディング、そして興味深い情報がたくさんあります。

28
Michael Randall

どちらのオプションも合法であり、各オプションには、他のオプションよりも効果的なシナリオがあります。

もちろん、非同期メソッドの結果を処理する場合、または現在のメソッドで発生する可能性のある例外を処理する場合は、常にawaitを使用してください。

public async Task Execute()
{
    try
    {
        await RunAsync();
    }
    catch (Exception ex)
    {
        // Handle thrown exception
    }
}

現在のメソッドで非同期メソッドの結果を使用しない場合-タスクを返します。このアプローチは、呼び出し側へのステートマシンの作成を遅らせるか、最終的なタスクが待ち受けられる場所で遅らせます。コメントで指摘したように、実行を少し効率的にすることができます。

しかし、結果を出して何もせず、起こりうる例外を処理したくない場合でも、タスクを待つ必要があるシナリオがあります。

public Task<Entity> GetEntity(int id)
{
    using (var context = _contextFactory.Create())
    {
        return context.Entities.FindAsync(id);
    }
}

上記のシナリオでは、FindAsyncは未完了のタスクを返すことができ、このタスクは呼び出し元にすぐに返され、contextステートメント内に作成されたusingオブジェクトを破棄します。
後で破棄されたオブジェクト(context)を使用しようとするため、呼び出し側がタスクを「待機」すると、例外がスローされます。

public async Task<Entity> GetEntity(int id)
{
    using (var context = _contextFactory.Create())
    {
        return await context.Entities.FindAsync(id);
    }
}

そして、伝統的に非同期待ちについての回答が必要ですスティーブンクリアリーのブログへのリンクを含める
Eliding Async and Await

13
Fabio

Awaitは、呼び出し側が非同期メソッドの結果を受け取って何かを実行できるようにするシーケンス機能です。非同期関数の結果を処理する必要がない場合は、それを待つ必要はありません。

あなたの例ではFunc1()Func2()は呼び出された非同期関数の戻り値を処理しないので、それらを待たなくても問題ありません。

1
Phillip Ngan

Awaitを使用すると、コードはasync関数の終了を待ちます。これは、次のような非同期関数の値が必要な場合に行う必要があります。

int salary = await CalculateSalary();

...

async Task<int> CalculateSalary()
{
    //Start high cpu usage task
    ...
    //End high cpu usage task
    return salary;
}

Awaitを使用しなかった場合、次のようになります。

int salary = CalculateSalary().Result;

...

async Task<int> CalculateSalary()
{
    //Start high cpu usage task
    ... //In some line of code the function finishes returning null because we didn't wait the function to finish
    return salary; //This never runs
}

Awaitは、この非同期関数が完了するまで待機することを意味します。

それをあなたのニーズに使用してください、あなたが情報値を割り当てるときあなたがコードが安全であるのを待つ限り、あなたのケース1と2は同じ結果を生み出します。

ソース: https://docs.Microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/index

Awaitは戻り値を期待しているので、2番目の方法で十分だと思います。 Func1()が値を返すのを待っているため、Func1()はすでに値を返しているFunc2()を実行しています。