3

先月、私は次の質問をしました。その結果、次のことを学びましたTaskEx.Yield

非同期メソッドは、最初の「待機」の前に高価なコードを持つことができますか?

ただし、このメソッドが実際には後続のすべてのコードをアンビエントに送信することに気付きましたTaskScheduler。真の DI 精神で、私たちのチームは可能な限りアンビエント インスタンスの使用を避けることに同意しましたTaskScheduler

次のようなものは素晴らしいでしょう:

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

ただし、Async CTP の現在の実装では、次のものしか提供されません。

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

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

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

2 に答える 2

5

真のDI精神で、私たちのチームは可能な限りアンビエントインスタンスの使用を避けることに同意しました...

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

あなたの選択肢:

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

期待どおりに動作しません。これにより、noopラムダが特定のキューに入れTaskSchedulerられ、暗黙のスケジューリングコンテキストでメソッドが再開されます。

以前のバージョンのAsyncCTPは、と呼ばれる「別のコンテキストへの譲歩」メソッドを提供していましたSwitchTo。誤用しやすいので削除しました。

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

PSたとえば、テスト目的で、独自のコンテキストを作成してインストールすることは(それほど)難しくありません。単体テストAsyncContextとコンソールプログラムの単純なスケジューリングコンテキストとして作成しました。Async CTPには、、、およびテスト用が付属しGeneralThreadAffineContextWindowsFormsContextWpfContextます。これらはいずれも、を使用してインストールできますSynchronizationContext.SetSynchronizationContext。IMO、DIはやり過ぎです。

于 2012-01-05T23:02:24.033 に答える
2

asyncの言語サポートにより、待機者は独自のスケジューリングを制御できます。タスクを待つときに開始されるデフォルトのスケジューリングがありますが、もう少しコードを追加してデフォルトの動作を変更する方法はたくさんあります。

具体的には、awaitは次のことを行うことを目的としています。

  1. 待機可能が「完了」しているかどうかをテストします(GetAwaiter()。IsCompleted)。
  2. 待機可能が実行されなかった場合(およびその場合のみ)、メソッドの残りの部分をスケジュールするように要求します(GetAwaiter()。OnCompleted(...))
  3. 待望の結果を「実現」します。これは、戻り値を返すか、発生した例外が再出現することを確認することを意味します。

したがって、待機可能であると主張された場合、それが「完了」したと主張した場合、何もスケジュールされないことに注意してください。

Task.Yield()は、今のところ実行を明示的に停止し、残りを実行のためにすぐにスケジュールする方法を提供するという明確な目的のために、決して実行されない待機可能なものを返すため、斬新です。アンビエントコンテキストを使用しますが、アンビエントコンテキストなしで同様に行う方法は他にもたくさんあります。

デフォルトの動作をオーバーライドする例は、不完全なタスクを待機しているが、ConfigureAwait(false)メソッドを使用している場合です。ConfigureAwait(false)は、常にデフォルトのタスクスケジューラを使用する特別な待機可能オブジェクトでタスクをラップし、事実上常にスレッドプールで再開します。'false'は、アンビエント同期コンテキストを明示的に無視するためにあります。

Task.Yield()。ConfigureAwait(false)はありませんが、次の仮定を検討してください。

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

上記はほとんどによって達成することができます

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

そこにはもう少し明確さとネストがありますが、何が起こっているかを考えると、これは必ずしも悪いことではありません。パート「A」は常に呼び出しスレッドで実行されますが、パート「B」は常にスレッドプールで実行されます。セクションAとセクションBにあるコードの見方には間違いなく違いがあるため、間にカーリーを追加すると、両方のセクションが同じコンテキストであると想定する前に、人々を一時停止させることができます。

于 2012-01-06T04:48:23.657 に答える