10

〜1秒待ってから前の写真を復元するよりも、 Windowsフォームウィンドウで写真を置き換えるスレッドを作成する必要があります。

次のコードだと思いました:

TaskScheduler ui = TaskScheduler.FromCurrentSynchronizationContext();
var task = Task.Factory.StartNew(() =>
{
    pic.Image = Properties.Resources.NEXT;
    Thread.Sleep(1000);
    pic.Image = Properties.Resources.PREV;
}, CancellationToken.None, TaskCreationOptions.LongRunning, ui)

仕事をしますが、残念ながらそうではありません。メイン UI スレッドをフリーズします。

これは、1 つのタスクに 1 つのスレッドが存在することが保証されていないためです。1 つのスレッドを複数のタスクの処理に使用できます。TaskCreationOptions.LongRunningオプションでさえ役に立ちません。

どうすれば修正できますか?

4

3 に答える 3

28

Thread.Sleepは同期遅延です。非同期遅延が必要な場合は、Task.Delayを使用してください。

現在ベータリリース中のC#5では、簡単に言うことができます

await Task.Delay(whatever);

非同期メソッドでは、メソッドは中断したところから自動的に再開します。

C#5を使用していない場合は、遅延を継続するために必要なコードを「手動で」設定できます。

于 2012-04-11T04:31:11.997 に答える
7

現在の同期コンテキストからの新しい 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 は、パフォーマンスのバランスを自動的に調整する素晴らしい仕事をします。

これまで見てきたように、デフォルトを使用する必要はありませんTaskSchedulerTaskSchedulerUI 、つまりを渡すTaskScheduler.FromCurrentSynchronizationContext()と、スレッド プールの代わりに UI スレッドが使用されますTaskScheduler.Default

これを念頭に置いて、コードをもう一度確認しましょう。

var task = Task.Factory.StartNew(() =>
                                     {
                                         pic.Image = Properties.Resources.NEXT;
                                     },
                                 CancellationToken.None,
                                 TaskCreationOptions.None,
                                 ui);

ここでは、リソースのプロパティを更新するUIスレッドで実行されるタスクを作成して開始しています。これを実行している間、UIは応答しなくなります。幸いなことに、これはおそらく非常に高速な操作であり、ユーザーは気付かないでしょう。Imagepic

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 スレッドでこれが呼び出され、画像が更新されるまでの非常に短い時間ブロックされます。

于 2012-04-10T23:07:36.813 に答える
5

Timer将来のある時点で UI タスクを実行するためにを使用する必要があります。1 秒間隔で 1 回実行するように設定するだけです。UI コードを tick イベントに入れ、それをオフにします。

本当にタスクを使用したい場合は、他のタスクを UI スレッドではなくバックグラウンドの脅威 (つまり、通常のStartNewタスク) で実行し、タスク内で Control.Invoke を使用して実行する必要があります。 UI スレッドのコマンド。ここでの問題は、タスクをスリープ状態にするためだけにタスクを開始するという根本的な問題をバンドエイドすることです。最初の場所で 1 秒間コードを実行することさえしない方がよいでしょう。

于 2012-04-10T23:03:35.530 に答える