web-dev-qa-db-ja.com

Web APIコントローラー(.netコア)でasync / awaitまたはタスクを使用する

返される集約オブジェクトを構築するコントローラーを備えた.netコアAPIがあります。作成するオブジェクトは、サービスクラスへの3つのメソッド呼び出しからのデータで構成されます。これらはすべて互いに独立しており、互いに分離して実行できます。現在、このコントローラーのパフォーマンスを向上させるためにタスクを使用しています。現在のバージョンは次のようになります...

[HttpGet]
public IActionResult myControllerAction()
{      
    var data1 = new sometype1();
    var data2 = new sometype2();
    var data3 = new List<sometype3>();

    var t1 = new Task(() => { data1 = service.getdata1(); });
    t1.Start();

    var t2 = new Task(() => { data2 = service.getdata2(); });
    t2.Start();

    var t3 = new Task(() => { data3 = service.getdata2(); });
    t3.Start();

    Task.WaitAll(t1, t2, t3);

    var data = new returnObject
    {
         d1 = data1,
         d2 = data2,
         d2 = data3
    };

    return Ok(data);
}

これはうまく機能しますが、ここでタスクを使用することが最善の解決策であるかどうか疑問に思っていますか? async/awaitを使用することは、より良いアイデアであり、より受け入れられた方法でしょうか?

たとえば、コントローラを非同期としてマークし、サービスメソッドの呼び出しごとに待機を設定する必要がありますか?

39
Ben Cameron

これはうまく機能しますが、ここでタスクを使用することが最善の解決策であるかどうか疑問に思っていますか? async/awaitを使用することは、より良いアイデアであり、より受け入れられた方法でしょうか?

そのとおり。 ASP.NETで並列処理を実行すると、要求ごとに複数のスレッドが消費され、スケーラビリティに大きな影響を与える可能性があります。非同期処理は、I/Oに対してはるかに優れています。

asyncを使用するには、まずサービス内の最低レベルの呼び出しから始めます。おそらく、ある時点でHTTP呼び出しを行っているのでしょう。非同期HTTP呼び出しを使用するように変更します(例:HttpClient)。その後、asyncをそこから自然に成長させます。

最終的には、非同期のgetdata1Asyncgetdata2Async、およびgetdata3Asyncメソッドになりますが、これらは同時に使用できます。

[HttpGet]
public async Task<IActionResult> myControllerAction()
{
  var t1 = service.getdata1Async();
  var t2 = service.getdata2Async();
  var t3 = service.getdata3Async();
  await Task.WhenAll(t1, t2, t3);

  var data = new returnObject
  {
    d1 = await t1,
    d2 = await t2,
    d3 = await t3
  };

  return Ok(data);
}

このアプローチでは、3つのサービス呼び出しが進行中に、myControllerActionfourではなくzeroスレッドを使用します。

60
Stephen Cleary
[HttpGet]
public async Task<IActionResult> GetAsync()
{      
    var t1 = Task.Run(() => service.getdata1());
    var t2 = Task.Run(() => service.getdata2());
    var t3 = Task.Run(() => service.getdata3());

    await Task.WhenAll(t1, t2, t3);

    var data = new returnObject
    {
        d1 = t1.Status == TaskStatus.RanToCompletion ? t1.Result : null,
        d2 = t2.Status == TaskStatus.RanToCompletion ? t2.Result : null,
        d3 = t3.Status == TaskStatus.RanToCompletion ? t3.Result : null
    };

   return Ok(data);
}
  1. タスクを待機しているとき、アクションスレッドは現在ブロックされています。 TaskWhenAllを使用して、待機可能なTaskオブジェクトを返します。したがって、非同期メソッドを使用すると、スレッドをブロックする代わりにタスクを待つことができます。
  2. ローカル変数を作成してタスクで割り当てる代わりに、Task<T>を使用して必要なタイプの結果を返すことができます。
  3. タスクを作成して実行する代わりに、Task<TResult>.Runメソッドを使用します
  4. アクション名には規則を使用することをお勧めします-アクションがGETリクエストを受け入れる場合、名前はGetで始まる必要があります
  5. 次に、タスクが正常に完了したかどうかを確認する必要があります。これは、タスクのステータスを確認することにより行われます。サンプルでは、​​タスクの一部が正常に完了しない場合、戻りオブジェクトプロパティにnull値を使用しました。別のアプローチを使用できます-例えば一部のタスクが失敗した場合にエラーを返します。
12

私が理解しているように、あなたはこれを並行して実行したいので、コードに何か問題があるとは思わない。ガブリエルが言ったように、タスクの完了を待つことができます。

[HttpGet]
public async Task<IActionResult> myControllerAction()
{      
  var data1 = new sometype1();
  var data2 = new sometype2();
  var data3 = new List<sometype3>();

  var t1 = Task.Run(() => { data1 = service.getdata1(); });
  var t2 = Task.Run(() => { data2 = service.getdata2(); });
  var t3 = Task.Run(() => { data3 = service.getdata3(); });

  await Task.WhenAll(t1, t2, t3); // otherwise a thread will be blocked here

  var data = new returnObject
  {
      d1 = data1,
      d2 = data2,
      d2 = data3
  };

 return Ok(data);
}

また、タスクの結果を使用して、コードのいくつかの行を保存し、コード全体を「より良い」ものにすることもできます(コメントを参照)。

[HttpGet]
public async Task<IActionResult> myControllerAction()
{      
  var t1 = Task.Run(() => service.getdata1() );
  var t2 = Task.Run(() => service.getdata2() );
  var t3 = Task.Run(() => service.getdata3() );

  await Task.WhenAll(t1, t2, t3); // otherwise a thread will be blocked here

  var data = new returnObject
  {
      d1 = t1.Result,
      d2 = t2.Result,
      d2 = t3.Result
  };

 return Ok(data);
}
1
Thomas D.