2012-01-05 18 views
3

先月私はTaskEx.Yieldの私の学習の結果、次の質問を:TaskEx.Yield(TaskScheduler)

Can async methods have expensive code before the first 'await'?

しかし、私は以来、このメソッドは、実際に周囲に後続のすべてのコードを提出することを実現していますTaskScheduler。真のDI精神では、私たちのチームは可能な限り周囲のインスタンスを使用することを避けることに同意しています。したがって、TaskSchedulerを明示的に指定できるかどうかを知りたいですか?次のような

何かが素晴らしいことだ:

public static YieldAwaitable Yield(TaskScheduler taskScheduler) 
{ 
    return new YieldAwaitable(taskScheduler); 
} 

しかし、非同期CTPの現在の実装では、唯一提供しています:

public static YieldAwaitable Yield() 
{ 
    return new YieldAwaitable(SynchronizationContext.Current ?? TaskScheduler.Current); 
} 

を以下に許容できる効率的な代替手段を提供しますか?

真DIの精神で
await Task.Factory.StartNew(() => { }, CancellationToken.None, TaskCreationOptions.None, this.TaskScheduler); 
+0

私はあなたの懸念を理解していますが、大抵の場合、周囲の 'TaskScheduler'をうまく使っていると思います。 – svick

答えて

5

、私たちのチームは、可能な場合は、周囲のインスタンスを使用しないように合意した...

非同期言​​語のサポートが暗黙のスケジューリングコンテキストに基づいています。私はここで依存性注入の必要性を見ていない。 asyncメソッドを呼び出すメソッドは、必要に応じて独自のコンテキストを提供できます。

あなたの代わり:予想通り

await Task.Factory.StartNew(() => { }, CancellationToken.None, TaskCreationOptions.None, this.TaskScheduler); 

は動作しません。これにより、noopラムダを特定のTaskSchedulerにキューイングし、暗黙のスケジューリングコンテキストでメソッドを再開します。

以前のバージョンのAsync CTPでは、SwitchToと呼ばれる「別のコンテキストへの利回り」メソッドが提供されていました。それは誤用が容易すぎるので、was removedです。

個人的には、暗黙的なスケジューリングコンテキストを使用してコードを保持することがよりクリーンであると思います。このコンテキストは、呼び出し元のメソッドによって提供されます。

P.S.テスト目的など、独自のコンテキストを作成してインストールするのは難しくありません。私はユニットテストとコンソールプログラムのためのシンプルなスケジューリングコンテキストとしてAsyncContextを書きました。非同期CTPには、GeneralThreadAffineContext,WindowsFormsContext、およびWpfContextが付属しています。これらのいずれもSynchronizationContext.SetSynchronizationContextを使用してインストールできます。 IMO、DIは過剰です。

+0

'...暗黙のスケジューリングコンテキストでメソッドを再開します。 - 私のテストによると、コードは常に最後の.StartNewまたは.Runで指定されたのと同じTaskSchedulerを使用しているように見えます。私は間違っていますか? –

+0

それは私が見るものではありません。 'TaskScheduler.Current' [戻る](http://msdn.microsoft.com/en-us/library/system.threading.tasks.taskscheduler.current.aspx)' TaskScheduler.Default'は、 (ほとんどの 'async'メソッドではそうです)。 –

+0

私が言わなかったのは、CopyToAsync(Stream)コールを待って、後続のコードが別のタスクスケジューラで実行されるという問題が最近発生したということです。私たちは、UIスレッドを使用する独自のタスクに注入されたTaskSchedulerを明示的に使用していました。しかし、デフォルトのスケジューラを定義しないと、CopyToAsyncメソッド(CTP拡張)がThreadPoolスケジューラに切り替わり、後続のすべてのコードで並行性が損なわれます。私たち自身に戻るには、この答えで引用した別の方法を使いました。問題を解決したようです。 –

2

asyncの言語サポートにより、待ち時間が自分自身のスケジューリングを制御できます。タスクを待っているときに起動するデフォルトのスケジューリングがありますが、コードを少し変更してデフォルトの動作を変更できる方法はたくさんあります。

具体的には、次の手順を実行することを意図している待つ:(。)GetAwaiter(完了する)awaitableが "済" であるかどうかを確認するために

  1. テスト。
  2. 待ち時間が終了していない場合は、GetAwaiter()。OnCompleted(...)メソッドの残りのスケジュールを依頼してください))
  3. 待望の結果を「実現」します。これは、返された値を返すか、例外が発生したことを保証することを意味します。

これは、「完了」と主張されている場合、何も予定されていないことに注意してください。それははあなたに明示的に今の実行を停止する方法を与えるという明確な目的のために、行わことはありませんawaitableをバック与え、すぐに実行するために、残りをスケジュールするため

Task.Yield()が新規であります。これは、周囲のコンテキストを使用しますが、周囲のコンテキストなしで同様に行う他の多くの方法があります。

デフォルトの動作を上書きする例は、不完全なタスクを待っているが、ConfigureAwait(false)メソッドを使用している場合です。 ConfigureAwait(false)は、常にがデフォルトのタスクスケジューラを使用し、事実上常にスレッドプールで再開するようにタスクをラップします。 「false」は、環境同期コンテキストを明示的に無視するためのものです。

ありTask.Yield()ConfigureAwait(偽)ではありませんが、以下の仮定のことを考慮してください。

// ... A ... 
await Task.Yield().ConfigureAwait(false); 
// ... B ... 

上記はかなりあり

// ... A ... 
await Task.Run(() => { 
    // ... B ... 
}); 

によって達成することができるが、もう少し具体化してそこに入れ子にしていますが、これは何が起きているかを考慮して必ずしも悪くはありません。パート 'A'は常に呼び出しスレッド上で実行され、パート 'B'は常にスレッドプール上で実行されます。セクションAとセクションBにあるコードをどのように見なければならないのかは明らかに違いがあります。そのため、複数のセクションが同じコンテキストであると仮定する前に、

+1

'await'のポイントは、あなたはそれのようなネストされたコードを書く必要がないということです。 – svick

+1

SwitchTo(...)が削除された(前のCTPと後で削除された)前提が全体的に妥当であり、.NET 4.5 Developer Previewには存在しません。 1つのブロックが以前とはまったく異なるコンテキストにある場合、実際にはブロック分離が有益です。たとえば、SwitchTo(...)がまだ残っているとします。これは、 "A()if(...){SwitchTo(...);} B();"その場合、A()は1つのコンテキスト内にあり、B()の前に別のコンテキストに切り替えることができます。 –

+0

SwitchTo(...)はcatchブロックに関する推論にさらに悪影響を与えます。現在、キャッチブロックにどのようなコンテキストが存在するかを見るために、スローの前にどの条件が当たったのかを推論する必要があります。 Task.Run(...)またはDispatcher.InvokeAsync(...)を必要とすると、代替コンテキストがより明示的になり、catchブロック内のあいまいなコンテキストを防ぐのに役立ちます。 –

関連する問題