web-dev-qa-db-ja.com

Thread.Sleepがそれほど有害な理由

Thread.Sleep();を使用すべきではないという記述をよく目にしますが、なぜそうなのか理解できません。 Thread.Sleep();が問題を引き起こす可能性がある場合、同じ結果が得られる安全な代替ソリューションはありますか?

例えば。

while(true)
{
    doSomework();
    i++;
    Thread.Sleep(5000);
}

別のものは:

while (true)
{
    string[] images = Directory.GetFiles(@"C:\Dir", "*.png");

    foreach (string image in images)
    {
        this.Invoke(() => this.Enabled = true);
        pictureBox1.Image = new Bitmap(image);
        Thread.Sleep(1000);
    }
}
110
Burimi

Thread.Sleepの呼び出しに関する問題は ここでは非常に簡潔に説明されています

Thread.Sleepの用途は次のとおりです。MTAスレッドでのテスト/デバッグ中に長時間の操作をシミュレートします。 .NETでは、他に使用する理由はありません。

Thread.Sleep(n)は、少なくともnミリ秒以内に発生する可能性があるタイムスライス(またはスレッドクォンタム)の数だけ現在のスレッドをブロックすることを意味します。タイムスライスの長さは、Windowsのバージョン/タイプおよびプロセッサによって異なり、通常15〜30ミリ秒の範囲です。これは、スレッドがnミリ秒以上ブロックすることがほぼ保証されていることを意味します。 nミリ秒後にスレッドが再び目覚める可能性は、ほぼ不可能に近い可能性があります。 そのため、Thread.Sleepはタイミングに意味がありません

スレッドは限られたリソースであり、作成に約200,000サイクル、破壊に約100,000サイクルかかります。デフォルトでは、スタック用に1メガバイトの仮想メモリを予約し、各コンテキストスイッチに2,000〜8,000サイクルを使用します。 これは、待機中のスレッドをhuge無駄にします。

推奨されるソリューション: WaitHandles

最も間違いは、Thread.Sleepをwhile-constructで使用することです( demo and answerNice blog-entry

編集:
答えを充実させたい:

2つの異なるユースケースがあります。

  1. 続行すべき特定のタイムスパンがわかっているため、待機しています(Thread.SleepSystem.Threading.Timerなどを使用)

  2. いくつかの条件がいつか変わるので、私たちは待っています...キーワードはいくらかの時間です!条件チェックがコードドメインにある場合は、WaitHandlesを使用する必要があります。そうでない場合、外部コンポーネントは何らかのフックを提供する必要があります...それが設計上悪い場合は!

私の答えは主にユースケース2をカバーしています

145

シナリオ1-非同期タスクの完了を待機:スレッドが別のスレッドのタスクの完了を待機しているシナリオで、WaitHandle/Auto | ManualResetEventを使用することに同意します。

シナリオ2-タイミングwhileループ:ただし、crudeタイミングメカニズム(while + Thread.Sleep)は、exactly whenを知る必要のないアプリケーションの99%に最適ですブロックされたスレッドは「ウェイクアップ*」する必要があります。スレッドを作成するのに200kサイクルかかるという引数も無効です-タイミングループスレッドを作成する必要があり、200kサイクルはもう1つの大きな数字です/ socket/db呼び出し?)。

それでは、while + Thread.Sleepが機能する場合、なぜ物事が複雑になるのでしょうか?構文弁護士のみが、実用的

32
Swab.Jat

私はコーディング政治の観点からこの質問に答えたいと思います。それは誰にとっても役立つかもしれません。しかし、特に9〜5人の企業プログラマを対象としたツールを扱う場合、ドキュメントを書く人は「あなたが本当に何を知っているのかを知らない限り、これをしてはいけない」「しない」などの言葉を使う傾向があります「やっている理由」。

C#の世界で私が気に入っているもう1つの点は、「lock(this)を呼び出さない」または「GC.Collect()を呼び出さない」ということです。これら2つは多くのブログや公式ドキュメントで強制的に宣言されており、IMOは完全な誤報です。いくつかのレベルでは、この誤った情報は目的を果たします。初心者は、代替手段を完全に研究する前に理解していないことをやめさせますが、同時に、すべての検索エンジンを介して実際の情報を見つけることを困難にします「なぜそうではないのか」という質問に対する答えを提供せずに、何かをしないように言っている記事を指しているようです。

政治的には、人々が「良いデザイン」または「悪いデザイン」と考えるものに要約されます。公式文書が私のアプリケーションの設計を決定するものであってはなりません。 sleep()を呼び出してはならないという技術的な理由がある場合、IMOは特定のシナリオで呼び出すことはまったく問題ないことをドキュメントに記載する必要がありますが、シナリオに依存しないか、他のシナリオに適した代替ソリューションを提供することもできますシナリオ。

「sleep()」の呼び出しは、デッドラインがリアルタイムで明確に定義されている多くの状況で役立ちますが、スリープを開始する前に考慮して理解する必要があるスレッドを待機および通知するためのより洗練されたシステムがあります( )、コードに不要なsleep()ステートメントをスローすることは、一般的に初心者の戦術と見なされます。

7
Jason Nelson

スリープは、制御できない独立したプログラムが一般的に使用されるリソース(ファイルなど)を使用する場合、プログラムが実行時にアクセスする必要がある場合、およびリソースがリソースを使用している場合に使用されますあなたのプログラムがそれを使用することをブロックされている他のプログラム。この場合、コードでリソースにアクセスする場合、リソースへのアクセスをtry-catchに入れ(リソースにアクセスできないときに例外をキャッチするため)、これをwhileループに入れます。リソースが空いている場合、スリープは呼び出されません。ただし、リソースがブロックされている場合は、適切な時間だけスリープし、リソースに再度アクセスしようとします(これがループしている理由です)。ただし、ループに何らかのリミッターを配置する必要があるため、無限ループになる可能性はないことに注意してください。制限条件をN回の試行に設定する(これは私が通常使用するものです)か、システムクロックを確認し、一定の時間を追加して制限時間を取得し、制限時間に達したらアクセスの試行を終了します。

3
Steve Greene

例の1).spinningおよび2).pollingループは、人々がThread.Sleep()部分ではなく、に注意することです。 Thread.Sleep()は通常、スピン中またはポーリングループ内のコードを簡単に改善するために追加されるため、単に「不良」コードに関連付けられていると思います。

さらに、人々は次のようなことをします:

while(inWait)Thread.Sleep(5000); 

ここで、変数inWaitはスレッドセーフな方法でアクセスされず、これも問題を引き起こします。

プログラマが見たいのは、イベントとシグナルおよびロックの構造によって制御されるスレッドです。そうすると、Thread.Sleep()が不要になり、スレッドセーフな変数アクセスに関する懸念もなくなります。例として、FileSystemWatcherクラスに関連付けられたイベントハンドラを作成し、イベントを使用してループの代わりに2番目の例をトリガーできますか?

Andreas N.が述べたように、 C#のスレッド化、Joe Albahariによる を読んで、本当に良いです。

3
mike