2017-01-31 15 views
22

返す集約オブジェクトを構築するコントローラを持つ.netコアAPIがあります。 作成するオブジェクトは、3つのメソッド呼び出しから得られたデータから成ります。これらはすべて互いに独立しており、互いに独立して実行できます。 現在、このコントローラーのパフォーマンスを改善するためにタスクを使用しています。現在のバージョンは次のようになりますタスクを使用すると、ここで最高のソリューションである場合、私は疑問に思って...しかしweb apiコントローラ(.netコア)でasync/awaitまたはtaskを使用

[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を使用する方が良いアイデアであり、より受け入れられる方法でしょうか?

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

+1

'service'は' getdata'メソッドのための非同期の代替を提供していませんか?また、一般的には、タスクの作成とそれを手動で開始するよりも 'Task.Run()'を優先するべきです。 –

+1

async/awaitはその意味で並列処理を助けません。実際には、3つのサービスリクエストをシリアル化します。しかし、 'Task.WaitAll'を実行すると、その行のスレッドを駐車しているので、助けになります。 –

+1

コールドタスクを作成してから、「開始」を呼び出す理由はまったくありません。それらはスレッドではありません。ちょうど 'Task.Run'を使用してください。 'await Task.WhenAll'を使用してください。 –

答えて

29

これは私はしかし、うまく機能タスクを使用するのが最善の解決策であるのだろうか? async/awaitを使用する方が良いアイデアであり、より受け入れられる方法でしょうか?

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

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

最終的に、あなたは、getdata1Async非同期getdata2Async、そのように同時に消費することができgetdata3Async方法、になってしまいます:3つのサービス・コールが進行中であるが、このアプローチで

[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); 
} 

myControllerActionの代わりに、の代わりにのスレッドを使用します。

+2

returnWindAll()*と*はreturnObject内の各タスクを待っていますか?なぜそれがちょうどどちらかを行うのに十分ではないかどうかは分かりません。説明していただけますか? :) –

+4

戻り値の型が異なる場合は、各タスクを '待つ'必要があります。私は 'Task.WhenAll'がコードをより明確にするので、この場合でも好む。もう少し効率的です(3回ではなく1回だけコンテキストを再開する)が、私の主な理由はコードの明確さです。 –

+0

@StephenCleary私のAPIがWaitForExitでSystem.Diagnostics.Processを呼び出す必要がある場合は、ゼロスレッドを使用する方法?回避策としてhttps://stackoverflow.com/a/10789196/241004を実装する予定です。あなたは良いアイデアを持っていますか? –

7
[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. あなたがタスクを待っているとき、あなたの行動のスレッドが現在ブロックされています。待ち受け可能なTaskオブジェクトを返すには、TaskWhenAllを使用します。したがって、非同期メソッドを使用すると、スレッドをブロックするのではなく、タスクを待つことができます。
  2. ローカル変数を作成してタスクに割り当てる代わりに、Task<T>を使用して必要な型の結果を返すことができます。代わりに作成してタスクを実行しているの
  3. が、私はアクション名の規則を使用することをお勧めしますTask<TResult>.Run方法
  4. を使用する - それが必要がGet
  5. 次で始まる名前だ、アクションが要求をGET受け入れる場合は、タスクが正常に完了したかどうか確認する必要があります。これは、タスクの状態をチェックすることによって行われます。私のサンプルでは、​​一部のタスクが正常に完了しなかった場合、戻りオブジェクトのプロパティにnullの値を使用しました。別のアプローチを使用することができます。一部のタスクが失敗した場合はエラーを返します。
+3

' RanToCompletion'のチェックを説明する必要があると思います。あなたは早期解約を予期していますか?フォールトされた呼び出しがあれば、 'await Task.WhenAll'は例外を発生させます –

+0

WebApiのgetメソッドの名前を変更できるだけですか? MVCでは、振る舞いを変更することはできません。 – Sefe

+0

@Sefeうん、あなたは正しい。それはmvc coreに気づかなかったのですが、少し前にweb-api関連の変更を元に戻す –

-1

私が理解しているように、あなたはこれを並列に実行したいので、あなたのコードに間違いはないと思います。ガブリエルが述べたように、あなたはタスクの仕上げを待つことができます。

[HttpGet] 
public async 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 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); 
} 
+0

最初のスニペットには、スローアウェイオブジェクトで変数を初期化し、それらの変数をキャプチャするといういくつかの問題があります。 –

+0

これは最適ではないことに同意します。そのため、私は2番目のコードスニペットで「より良い」ソリューションを提供しました。とにかく、私は最初のスナップが本当の問題を抱えているとは思わない。コンパイラは、使い捨てのローカル変数をプライベートフィールドにします。これは正常に動作します。 (やはり理想的ではないことは分かっています) –

関連する問題