2016-02-22 4 views
5

私はC#でいくつかの非同期プログラミングを見ていますが、まったく同じことをしていて、すべて待っているこれらの関数の違いは何か不思議でした。これらの魅力的な方法の違いは何ですか?

public Task<Bar> GetBar(string fooId) 
{ 
    return Task.Run(() => 
    { 
     var fooService = new FooService(); 
     var bar = fooService.GetBar(fooId); 
     return bar; 
    }); 
} 

public Task<Bar> GetBar(string fooId) 
{ 
    var fooService = new FooService(); 
    var bar = fooService.GetBar(fooId); 
    return Task.FromResult(bar) 
} 

public async Task<Bar> GetBar(string fooId) 
{ 
    return await Task.Run(() => 
    { 
     var fooService = new FooService(); 
     var bar = fooService.GetBar(fooId); 
     return bar; 
    }); 
} 

私の推測では、最初は、物事を行うには正しい方法で、あなたが返されるタスクから結果を取得しようとするまでのコードが実行されていないということです。

コールでコードが実行され、結果が返されたタスクに格納されます。

第3の種類は第2の種類のようですか?コードはコール時に実行され、Task.Runの結果はリターンですか?その場合、この機能は愚かなものでしょうか?

私は正しいのですか?

+1

最初は非同期で実行され、2番目は実行されずブロックされます。結果はタスクに同期してラップされて返されます。 3つ目は最初のものとほとんど同じです - 'async/await'は単純にawatingを簡単にします。それは非同期的なものではありません。実際に関数 –

+0

PSの中で結果が使用されないので、3番目のものは実際には不要なひょうたんを持っています。 –

+1

注:これは "async over sync"です:バックエンドサービス( 'GetBar')は基本的に同期APIであり、あなたはそれを公開しています。 'async'シムこれを行う*唯一の理由は、UIのペイントを解放するようなものです。これは、より広範な 'async'目標を達成していません。あなたはまだ**スレッドが結果をブロックしています - 元の*スレッドではありません。 –

答えて

6

これらの方法の実装は意味をなさない。あなたがしているのは、ブロックプールをスレッドプールにプッシュすることです(悪い場合は、同期して実行し、結果をTask<Bar>インスタンスにラップします)。代わりに、同期APIを公開し、呼び出し元に呼び出し方法を決定させる必要があります。彼らがTask.Runを使用するかどうかは、それらに依存します。

#1

(直接Task.Runを介して作成Task<Bar>を返す)第一の変形はそれを行わない場合でも、「純粋」である:

は、ここに違いがあることを言いましたAPIの観点からは非常に意味があります。 Task.Runは、スレッドプール上の所定の作業をスケジュールし、非同期操作の完了を表すTask<Bar>を呼び出し側に返します。

#2

Task.FromResultを利用する)第二の方法は、非同期ではありません。これは、通常のメソッド呼び出しと同様に、同期的に実行されます。結果は、完成したTask<Bar>インスタンスで単純にラップされます。

#3

これは最初のより入り組んだバージョンです。あなたは#1と同様の結果を達成していますが、余分な、不必要な、さらには幾分危険なawaitがあります。これはもっと詳しく調べる価値があります。

async/awaitは、単一のユニット(Task)に非同期の作業を表す複数Task Sを組み合わせる介し非同期動作を連鎖に最適です。これは、正しい順序で何かが起きるのを助け、非同期操作間の豊富な制御フローを提供し、正しいスレッドで事態が発生するのを確実にします。

にはTaskが1つのみ含まれているため、上記のいずれにも該当しません。したがって、コンパイラがTask.Runがすでに実行していることを達成するために、ステートマシンを生成する必要はありません。

asyncの方法がうまく設計されていても危険です。 await ed TaskConfigureAwait(false)を使用しないと、偶然、SynchronizationContextのキャプチャ、強制終了のパフォーマンスが導入され、デッドロックのリスクが導入されることはありません。

あなたの呼び出し側がGetBar(fooId).Wait()またはGetBar(fooId).Result経由SynchronizationContext(すなわちウィンフォーム、WPF、おそらく ASP.NET)を持っている環境で、あなたのTask<Bar>をブロックすることを決定した場合、彼らはhere説明する理由のためにデッドロックを取得します。

+0

これは単なる例であり、おそらくより多くのコードがTask.Run()でラップされて実行されるでしょう。私が記事を解釈した場合、デッドロックに言及したのは、私がした場合にのみ表示されます。 var bar = GetBar(fooId); bar.Result; 私がしている場合: var bar = await GetBar(fooId);非同期メソッドでは、それは発生しませんか? – Gralov

+1

@Gralov、正解、方法1と3で生成された 'Task 'が 'await'という名前で正常です。メソッド#2で生成された 'Task 'も、技術的には*可能ですが、大部分が同期して実行されるため、全く意味がありません。 'async'を使用していて、何のメリットもありません)。 –

0

私は次のアナロジーのコメントでStackoverflowのどこかを読んでいます。それは私が簡単にそれを見つけることができないコメントにあるので、それへのリンクはありません。

朝食を取る必要があるとします。いくつかの卵を沸かしパンを焼く。

あなたが卵を沸騰起動した場合、その後、どこかのサブルーチン「ボイルエッグ」であなたは卵が

を煮沸するまで待つ必要があるでしょう

同期は卵がサブルーチンを開始する前に煮沸終了するまで、あなたが待っていることだろう"トーストパン"。

しかし、卵が沸騰している間は、待たずに卵を焼き始めると効率的です。その後、いずれかが終了するのを待って、最初に終了したプロセス「卵を沸かす」または「トーストブレッド」を続行します。 これは非同期ですが、同時ではありません。それはまだすべてをやっている一人の人です。

第3の方法は、パンを焼くときに卵を沸かす料理人を雇うことです。これは本当に同時です.2人が何かしています。もしあなたが本当に金持ちなら、あなたは新聞を読んでいる間にトースターを雇うこともできますが、ねえ、Downton Abbeyに住んでいるわけではありません;-)

に戻る

Nr 2:同期:メインスレッドがすべての作業を行います。このスレッドは、呼び出し元が何か他のことを行う前に卵が沸いた後に戻ります。

Nr1は非同期として宣言されていません。つまり、作業を行う別のスレッドを開始しても、呼び出し側は何か他の処理を続けることはできませんが、できますが、卵が沸騰するまで待つだけです。

3番目の手順は非同期と宣言されています。これは、卵を待つとすぐに、あなたの呼び出し元は、パンを焼くような何かをすることができることを意味します。これはすべての作業が1つのスレッドで行われることに注意してください。

あなたの呼び出し元が何もしないで待っても、あなたの呼び出し元が非同期と宣言されていない限り、それはあまり役に立たないでしょう。これは、発呼者の発呼者に何か他のことをする機会を与える。代わりにTResult の非同期タスクの代わりに、ボイド、およびタスク< TResultを返すと宣言されたすべての機能> - - 一つだけ例外があります:非同期待つ適切に使用している場合

は一般的に、あなたは以下を参照してくださいイベントハンドラは、タスクの代わりにvoidを返します。 - 非同期関数を呼び出す関数はすべて非同期であると宣言する必要があります。そうでない場合はasync-awaitを使用すると便利ではありません - 非同期メソッドを呼び出した後、卵が煮沸されている間パンを焼くことができます。パンが焼かれたら、卵を待つことができます。または、卵がトースト中に準備が整っているかどうかを確認することができます。あるいは、最も効率的な方法でTask.WhenAnyを卵やトーストの仕上げを続けるか、タスクを待つことができます。いつでも、両方が終わっていない限り、何か役に立つものがないときはすべて。

この類似性は役に立ちます

+0

#1の説明が間違っています。この変形では、 "タスク"を呼び出し元に返す前に(少なくとも "main"スレッド上の)スレッドを同期させて(少なくとも "*"の)スレッドを同期させます。 #3では、メソッドが同期的に動作することが保証されている部分はやや太っています。「Main.」スレッド上で「Task.Run」の呼び出しが行われますが、それに続く*最終的に「タスク」が呼び出し元に返される前に、「メイン」スレッド上でも、関連する作業( 'GetAwaiter'、チェック' IsCompleted'、スケジュールの継続)を行います。 –

+0

朝食のアナロジーは、非同期待ちの(主な)目的ではないマルチスレッドの導入を示唆しています。 – MEMark

関連する問題