現在の同期コンテキストからの新しい TaskScheduler を渡すと、実際にはタスクを UI スレッドで実行するように指示します。あなたは実際にそれをしたいので、UI コンポーネントを更新できますが、そのスレッドはブロックされるため、そのスレッドでスリープしたくありません。
これは、.ContinueWith
が理想的な場合の良い例です。
TaskScheduler ui = TaskScheduler.FromCurrentSynchronizationContext();
var task = Task.Factory.StartNew(() =>
{
pic.Image = Properties.Resources.NEXT;
},
CancellationToken.None,
TaskCreationOptions.None,
ui);
task.ContinueWith(t => Thread.Sleep(1000), TaskScheduler.Default)
.ContinueWith(t =>
{
pic.Image = Properties.Resources.Prev;
}, ui);
編集(いくつかのものを削除してこれを追加):
何が起こるかというと、更新するのに十分な時間だけ UI スレッドをブロックしているということですpic.Image
。を指定することTaskScheduler
で、タスクを実行するスレッドを指定します。タスクとスレッドの関係は 1 対 1 ではないことに注意してください。実際、1000 個以下の比較的少数のスレッドで 1000 個のタスクを実行することができます。これは、各タスクの作業量によって異なります。作成する各タスクが個別のスレッドで実行されると想定しないでください。CLR は、パフォーマンスのバランスを自動的に調整する素晴らしい仕事をします。
これまで見てきたように、デフォルトを使用する必要はありませんTaskScheduler
。TaskScheduler
UI 、つまりを渡すTaskScheduler.FromCurrentSynchronizationContext()
と、スレッド プールの代わりに UI スレッドが使用されますTaskScheduler.Default
。
これを念頭に置いて、コードをもう一度確認しましょう。
var task = Task.Factory.StartNew(() =>
{
pic.Image = Properties.Resources.NEXT;
},
CancellationToken.None,
TaskCreationOptions.None,
ui);
ここでは、リソースのプロパティを更新するUIスレッドで実行されるタスクを作成して開始しています。これを実行している間、UIは応答しなくなります。幸いなことに、これはおそらく非常に高速な操作であり、ユーザーは気付かないでしょう。Image
pic
task.ContinueWith(t => Thread.Sleep(1000), TaskScheduler.Default)
.ContinueWith(t =>
{
pic.Image = Properties.Resources.Prev;
}, ui);
このコードでは、ContinueWith
メソッドを呼び出しています。それはまさにそのように聞こえます。Task
実行時にラムダ パラメータを実行する新しいオブジェクトを返します。タスクが完了、失敗、またはキャンセルされたときに開始されます。を渡すことで、いつ実行するかを制御できますTaskContinuationOptions
。ただし、以前と同じように、別のタスク スケジューラも渡しています。これは、スレッド プール スレッドでタスクを実行する既定のタスク スケジューラであるため、UI をブロックしません。このタスクは何時間も実行される可能性があり、対話している UI スレッドとは別のスレッドであるため、UI は応答性を維持します (放置しないでください)。
ContinueWith
また、既定のタスク スケジューラで実行するように設定したタスクを呼び出しました。同じ UI タスク スケジューラを実行中のタスクに渡したので、これは UI スレッドで画像を再度更新するタスクです。スレッドプール タスクが完了すると、UI スレッドでこれが呼び出され、画像が更新されるまでの非常に短い時間ブロックされます。