2016-09-02 3 views
7

最初はすべての継続がスレッドプール上で実行されると考えました(デフォルトの同期コンテキストが与えられています)。しかし、これは私がTaskCompletionSourceを使用した場合には当てはまらないようです。この場合、TaskCompletionSource.SetResult()は継続を同期して実行しますか?

私のコードは次のようなものになります。

Task<int> Foo() { 
    _tcs = new TaskCompletionSource<int>(); 
    return _tcs.Task; 
} 

async void Bar() { 
    Console.WriteLine(Thread.Current.ManagedThreadId); 
    Console.WriteLine($"{Thread.Current.ManagedThreadId} - {await Foo()}"); 
} 

Barを返されたタスクIsComplete = falseを意味し、特定のスレッドといくつかの時間のための設定を解除TaskCompletionSource滞在に呼び出されます。その後、しばらくしてから同じスレッドが_tcs.SetResult(x)にコールされます。これは私の理解ではスレッドプール上で継続を実行する必要があります。

私のアプリケーションで観察したことは、継続が正常に同期されているかのように、継続を実行しているスレッドが実際には同じスレッドであることです。SetResultが呼び出されました。

私はさらにSetResultにブレークポイントを設定し、それを踏み越えて(そ​​して継続中にブレークポイントを持つ)、実際に継続を連続して呼び出すことになりました。

正確にSetResult()が直ちに継続を連続して呼び出すのですか?

+0

これは、その半分ではなく[mcve]を提供すると助けになるので、私たち自身で実験することができます。 –

答えて

4

SetResult通常は、TCSから継続的に実行されます。主な例外は、がTCSを作成するときにTaskContinuationOptions.RunContinuationsAsynchronouslyフラグを明示的に渡した場合です(.NETで新しい4.6)。他のシナリオでは、非同期で動作させるのは、現在のスレッドが破滅していると考えられる場合です。

これは非常に重要です。慎重でないと、呼び出し元コードが他の作業(ソケットIOを扱うなど)を行うスレッドを制御できなくなる可能性があるからです。

+0

ところで、あなたは 'SetResult()'を元の待っている発信者ではありませんか?継続は、デフォルトの 'ConfigureAwait(...)'のために元の呼び出し元のスレッドに行き着かないといけませんか? – Petrroll

+0

@Petrroll関数が呼び出されたときに 'SyncronisationContext.Current'がなければ、元のスレッドはありません。 –

+0

そして、明示的なSyncContextがあれば? – Petrroll

5

最初は、すべての継続がスレッドプール(デフォルトの同期コンテキストが与えられている)で実行されると考えました。しかし、これはTaskCompletionSourceを使用する場合には当てはまりません。

実際にはawaitを使用すると、ほとんどの継続が同期して実行されます。

マークの答えは素晴らしいです。ちょっと詳しく説明したかっただけです...

TaskCompletionSource<T>は、デフォルトでSet*が呼び出されたときに同期して動作します。 Set*は、タスクを完了し、は、1回のメソッド呼び出しで継続を発行します。これは、ロックを保持しているSet*を呼び出すとデッドロックのレシピであることを意味します。

実際に実行される場合と実行されない場合があるため、「継続を発行する」という奇妙なフレーズを使用します。それについては後で詳しく説明します。

フラグTaskCreationOptions.RunContinuationsAsynchronouslyフラグは、TaskCompletionSource<T>に非同期に継続を発行するよう指示します。これは、継続の発行(これはSet*呼び出しによってのみトリガされる)からのタスクの完了(これはまだSet*によって直ちに行われる)を分解する。したがってRunContinuationsAsynchronouslyの場合、Set*コールはタスクを完了するだけです。継続を同期して実行しません。これは、ロックを保持している間にSet*を呼び出すことが安全であることを意味します。

しかし、継続を同期して発行するデフォルトのケースに戻ってください。

各継続にフラグがあります。デフォルトでは、継続はと非同期に実行されますが、TaskContinuationOptions.ExecuteSynchronouslyによって同期させることができます。 (awaitdoes use this flag - 私のブログにはリンクがありますが、これは実装上の詳細であり、公式には文書化されていません)。

しかし、ExecuteSynchronouslyが指定されている場合でも、a number of situations where the continuation is not executed synchronouslyがあります

  • が継続に関連したTaskSchedulerがある場合は、そのスケジューラは、タスクがある場合には、現在のスレッドを拒否するオプションを与えていますは、同期して実行する代わりにをそのTaskSchedulerにキューイングします。
  • 現在のスレッドがアボートされている場合、タスクは他の場所でキューに入れられます。
  • 現在のスレッドのスタックが深すぎる場合、タスクは他の場所でキューに入れられます。 (これはヒューリスティックであり、StackOverflowExceptionを避けることはできません)。

    • TaskCompletionSource<T>RunContinuationsAsynchronouslyを指定しません:。かなりの数の条件だが、あなたの簡単なコンソールアプリケーションのテストで、それらはすべて満たしている

  • 続き(await)はExecuteSynchronouslyとなります。
  • 続きにはTaskSchedulerが指定されていません。
  • ターゲットスレッドは継続を実行できます(中止されず、スタックはOKです)。

通常、私はTaskCompletionSource<T>のいずれかの使用法はTaskCreationOptions.RunContinuationsAsynchronouslyと指定する必要があります。個人的には、セマンティクスはその旗で、より適切で、あまり驚くべきものではないと私は思う。

関連する問題