web-dev-qa-db-ja.com

Web APIで非同期待機を使用するためのベストプラクティス

サービスレイヤーとして.NETコアWeb APIがあります。サービス層にはすべてのEFコードがあります。

このコードのベースコントローラーがある場合

protected Task<IActionResult> NewTask(Func<IActionResult> callback)
{
    return Task.Factory.StartNew(() =>
    {
        try
        {
            return callback();
        }
        catch (Exception ex)
        {
            Logger.LogError(ex.ToString());
            throw;
        }
    });
}

コントローラーアクションでは、上記のメソッドでサービスへのすべての呼び出しをラップします。 :

[HttpGet("something")]
public async Task<IActionResult> GetSomething(int somethingId)
{
    return await NewTask(() =>
    {
        var result = _somethingService.GetSomething(somethingId);

        if (result != null)
            return Ok(result);
        else
            return NotFound("Role not found");
    });
}

明日を考えると、これは正しいパターンですか?複数のサービス呼び出しが実行されているか、他のWebサービスを呼び出すことがありますお知らせ下さい。

10
krishna

私は私のAPIに非同期待機の恩恵を受けたいです。上記のパターンはこれらのニーズに応えます

いいえ、違います。スレッドプールで同期作業を実行すると、同期and非同期コードの欠点が得られますが、どちらもメリットはありません。

何かのサービスには、entityframeworkコアを使用するいくつかのcrud操作があります

現在、あなたのアクションメソッドは私が「偽の非同期」と呼んでいるものです-それは非同期に見えますが(例えば、awaitを使用して)、実際にはバックグラウンドスレッドでブロッキングコードを実行しています。 ASP.NETでは、真の非同期が必要です。whcnは、常に非同期でなければならないことを意味します。 ASP.NETでこれが悪い理由の詳細については、ASP.NETのasyncイントロの前半を参照してください (ほとんどASP.NETを扱っています)非コアですが、同期リクエストと非同期リクエストについての最初の部分は、あらゆる種類のサーバーに有効です)。

これを完全に非同期にするには、最低レベルで開始する必要があります-この場合、EFCoreを呼び出します。それらはすべて非同期をサポートします。したがって、x.FirstOrDefault()のようなAPI呼び出しをawait x.FirstOrDefaultAsync()に置き換えてください(すべての作成/更新/削除などでも同じです)。

次に、async/awaitがそこから自然に成長できるようにします。コンパイラーがガイドします。 somethingServiceで非同期メソッドを使用すると、次のように使用できます。

[HttpGet("something")]
public async Task<IActionResult> GetSomething(int somethingId)
{
  var result = await _somethingService.GetSomethingAsync(somethingId);
  if (result != null)
    return Ok(result);
  else
    return NotFound("Role not found");
}
21
Stephen Cleary

さて、まず第一に、スレッドプールスレッドで実行したいCPUバウンドの重い作業がある場合にのみ、Task.Factory.StartNewの使用を停止し、Task.Runを使用する必要があります。あなたの場合、あなたは本当にそれをまったく必要としません。また、メソッドの実装ではなく、メソッドを呼び出すときにのみTask.Runを使用する必要があることに注意してください。詳細については、こちらをご覧ください こちら

あなたが本当に必要なのは、実際にデータベースを呼び出してasync/awaitを使用したいときに、サービス内で非同期の作業をすることです(あなたのケースでサービスが必要かどうかは本当にわかりません)バックグラウンドスレッドで何かを実行するだけではありません。

基本的に、サービスは次のようになります(サービスが必要なことが確実な場合)。

class PeopleService
{
    public async Task<Person> GetPersonByIdAsync(int id)
    {
        Person randomPerson = await DataContext.People.FirstOrDefaultAsync(x => x.Id == id);
        return randomPerson;
    }
}

ご覧のとおり、サービスがデータベースに対して非同期呼び出しを行うようになりました。これが基本的なパターンです。これをすべての操作(追加/削除/など)に適用できます。

サービスを非同期にすると、アクションでデータを簡単に消費できるようになります。

アクションは次のようになります。

[HttpGet("something")]
public async Task<IActionResult> GetPerson(int id)
{
    var result = await PeopleService.GetPersonByIdAsync(id);

    if (result != null)
        return Ok(result);
    else
        return NotFound("Role not found");
}
6
Kerim Emurla