web-dev-qa-db-ja.com

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

私は私のWeb APIプロジェクトでASP.NETのasync/await機能を利用しようとしています。それが私の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の正しい使い方であるかどうかはわかりません。あなたの考えを共有してください。

91
arp

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

サーバー側の非同期コードの主な利点は、スケーラビリティです。それはあなたの要求を速く走らせることを魔法のようにはしません。私の asyncASP.NETに関する記事 _では、「asyncname__を使用する必要がある」という考慮事項についていくつか説明します。

あなたのユースケース(他のAPIを呼び出す)は非同期コードに非常に適していると思いますが、「非同期」は「速い」という意味ではないことに留意してください。最善の方法は、最初にUIの応答性と非同期性を高めることです。これはあなたのアプリを少し遅くしても速く感じさせるでしょう。

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

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

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

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)で非同期にします。それからasyncname__を少しずつ動かして、最後のステップとしてあなたのコントローラアクションを非同期にします。

そして、いかなる状況においても、このシナリオでTask.Runを使うべきではありません。

166
Stephen Cleary

それは正しいですが、おそらく役に立ちません。

待つべきものは何もない - 非同期に動作する可能性のあるブロッキング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は、ずっと先に進む必要があるものです。既存のシステムに後から合わせることは非常に困難です。

9
Richard

要求スレッドは同期メソッドの実行中にブロックされるため、async/awaitを効果的に活用していませんReturnAllCountries()

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

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

0
James Wierzba