ASP.NET Web APIでasync / awaitを効果的に使用する


114

async/await私のWeb APIプロジェクトでASP.NET の機能を利用しようとしています。それが私のWeb APIサービスのパフォーマンスに影響を与えるかどうかは、私にはよくわかりません。アプリケーションのワークフローとサンプルコードを以下に示します。

ワークフロー:

UIアプリケーション→Web APIエンドポイント(コントローラー)→Web APIサービスレイヤーのメソッドの呼び出し→別の外部Webサービスを呼び出します。(ここに、DBインタラクションなどがあります。)

コントローラ:

public async Task<IHttpActionResult> GetCountries()
{
    var allCountrys = await CountryDataService.ReturnAllCountries();

    if (allCountrys.Success)
    {
        return Ok(allCountrys.Domain);
    }

    return InternalServerError();
}

サービス層:

public Task<BackOfficeResponse<List<Country>>> ReturnAllCountries()
{
    var response = _service.Process<List<Country>>(BackOfficeEndpoint.CountryEndpoint, "returnCountries");

    return Task.FromResult(response);
}

上記のコードをテストし、動作しています。しかし、それがの正しい使い方かどうかはわかりませんasync/await。あなたの考えを共有してください。


回答:


202

それが私のAPIのパフォーマンスに影響を与えるかどうか、私にはよくわかりません。

サーバー側の非同期コードの主な利点はスケーラビリティであることを覚えておいてください。それは魔法のようにあなたのリクエストがより速く走るようにはしません。ASP.NET に関する記事asyncで、「使用すべき」考慮事項について説明しasyncます

あなたのユースケース(他のAPIを呼び出す)は非同期コードに適していると思いますが、「非同期」は「高速」ではないことに注意してください。最善の方法は、最初にUIを応答可能で非同期にすることです。これはあなたのアプリを感じさせます、それはわずかに遅いだ場合でも速いです。

コードに関する限り、これは非同期ではありません。

public Task<BackOfficeResponse<List<Country>>> ReturnAllCountries()
{
  var response = _service.Process<List<Country>>(BackOfficeEndpoint.CountryEndpoint, "returnCountries");
  return Task.FromResult(response);
}

のスケーラビリティの利点を得るには、真に非同期の実装が必要ですasync

public async Task<BackOfficeResponse<List<Country>>> ReturnAllCountriesAsync()
{
  return await _service.ProcessAsync<List<Country>>(BackOfficeEndpoint.CountryEndpoint, "returnCountries");
}

または(このメソッドのロジックが本当に単なるパススルーの場合):

public Task<BackOfficeResponse<List<Country>>> ReturnAllCountriesAsync()
{
  return _service.ProcessAsync<List<Country>>(BackOfficeEndpoint.CountryEndpoint, "returnCountries");
}

このように「外側から」よりも「内側から」から作業するほうが簡単であることに注意してください。つまり、非同期のコントローラアクションから始めて、ダウンストリームのメソッドを強制的に非同期にしないでください。代わりに、自然に非同期の操作(外部API、データベースクエリなどを呼び出す)を識別し、それらを最初に最低レベルで非同期にします(Service.ProcessAsync)。次に、async細流化して、コントローラのアクションを最後のステップとして非同期にします。

そして、どのような状況Task.Runでもこのシナリオで使用すべきではありません。


4
スティーブン、貴重なコメントをありがとう。すべてのサービスレイヤーメソッドを非同期に変更しましたが、ExecuteTaskAsyncメソッドを使用して外部REST呼び出しを呼び出し、期待どおりに動作しています。また、非同期とタスクに関するブログ投稿にも感謝します。最初に理解するのにとても役立ちました。
arp 2015

5
ここでTask.Runを使用しない理由を説明できますか?
Maarten 2016年

1
@Maarten:私がリンクした記事でそれを(簡単に)説明ます。私は私のブログでより詳細に行きます。
Stephen Cleary

私はあなたの記事スティーブンを読みました、そしてそれは何かに言及することを避けているようです。ASP.NET要求が到着してスレッドプールスレッドで実行を開始すると、問題ありません。しかし、非同期になると、はい、非同期処理を開始し、そのスレッドはすぐにプールに戻ります。しかし、asyncメソッドで実行される作業は、それ自体がスレッドプールスレッドで実行されます。
ヒュー


12

それは正しいですが、おそらく役に立たないでしょう。

待つものは何もない-非同期に動作する可能性のあるブロッキングAPIへの呼び出しがない-非同期操作(オーバーヘッドがある)を追跡するように構造を設定していますが、その機能を利用していません。

たとえば、サービスレイヤーが非同期呼び出しをサポートするEntity FrameworkでDB操作を実行していた場合:

public Task<BackOfficeResponse<List<Country>>> ReturnAllCountries()
{
    using (db = myDBContext.Get()) {
      var list = await db.Countries.Where(condition).ToListAsync();

       return list;
    }
}

dbが照会されている間(したがって、別の要求を処理できる間)、ワーカースレッドは何か他のことを実行できます。

Awaitは、ずっと下に行く必要があるものになる傾向があります。既存のシステムにレトロフィットするのは非常に困難です。


-1

同期メソッドの実行中に要求スレッドがブロックされるため、非同期/待機を効果的に利用していないReturnAllCountries()

リクエストを処理するために割り当てられたスレッドは、ReturnAllCountries()機能している間、アイドル状態で待機しています。

ReturnAllCountries()非同期に実装できる場合は、スケーラビリティのメリットが得られます。これは、スレッドのReturnAllCountries()実行中に.NETスレッドプールに解放されて、別の要求を処理できるためです。これにより、スレッドをより効率的に利用して、サービスのスループットを向上させることができます。


本当じゃない。ソケットは接続されていますが、要求を処理するスレッドは、サービス呼び出しを待機している間に、別の要求を処理するなど、他の何かを行うことができます。
Garr Godfrey、

-8

サービスレイヤーを次のように変更します。

public Task<BackOfficeResponse<List<Country>>> ReturnAllCountries()
{
    return Task.Run(() =>
    {
        return _service.Process<List<Country>>(BackOfficeEndpoint.CountryEndpoint, "returnCountries");
    }      
}

あなたがそれを持っているので、あなたはまだあなたの_service.Process呼び出しを同期的に実行していて、それを待つことからほとんどまたは全く利益を得ていません。

このアプローチでは、低速になる可能性のある呼び出しをでラップしてTask開始し、待機するように戻します。今、あなたはを待つことの利益を得ますTask


3
ブログリンクのとおり、Task.Runを使用しても、メソッドは引き続き同期的に実行されていることがわかりましたか?
arp 2015

うーん。上記のコードとそのリンクの間には多くの違いがあります。あなたServiceは非同期メソッドではなく、Service呼び出し自体の中で待っていません。彼の主張は理解できるが、ここでは当てはまらないと思う。
ジョネソポリス2015

8
Task.RunASP.NETでは避けてください。このアプローチは、async/ からすべての利点を取り除きawait、実際には単なる同期呼び出しよりも負荷がかかるとパフォーマンスが低下します。
Stephen Cleary 2015
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.