Thread.Sleepがなぜ有害なのか


128

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);
    }
}

3
ブログの要約は「Thread.sleep()を誤用しないでください」かもしれません。
マーティンジェームズ

5
有害だとは言えません。私はむしろそれのようなものだと言いたいです、goto:すなわちおそらくあなたの問題に対するより良い解決策があるでしょうSleep
デフォルトの

9
とはまったく同じでgotoはありません。これは、デザインの匂いではなくコードの匂いのようです。gotoコードにsを挿入するコンパイラに問題はありません。コンピュータが混乱することはありません。しかしThread.Sleep、まったく同じではありません。コンパイラはその呼び出しを挿入せず、他の悪影響があります。しかし、はい、ほとんどの場合、より良い解決策が確かに正しいので、それを使用することは間違っているという一般的な感情。
コーディグレイ

31
上記の例が悪い理由については誰もが意見を述べていますが、与えられた例の目標を達成するThread.Sleep()を使用しない書き直されたバージョンを提供した人はいません。
StingyJack

3
sleep()コード(またはテスト)を挿入するたびに子犬が死ぬ
Reza S

回答:


163

通話に問題がThread.Sleepされているここでは、非常に簡潔に説明しました

Thread.SleepMTAスレッドでのテスト/デバッグ中に長い操作をシミュレートするという用途があります。.NETでは、それを使用する他の理由はありません。

Thread.Sleep(n)つまり、n ミリ秒以内に発生する可能性のあるタイムスライス(またはスレッドクォンタム)の数だけ現在のスレッドをブロックします。タイムスライスの長さは、Windowsのバージョン/タイプやプロセッサによって異なり、通常は15〜30ミリ秒です。これは、スレッドがnミリ秒以上ブロックすることがほぼ保証されていることを意味します。スレッドがnミリ秒後に正確に再起動される可能性は、不可能と同じくらい不可能です。 したがって、Thread.Sleepタイミングは無意味です

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

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

ほとんどの間違いはThread.Sleep、while-constructで使用していますデモと回答素敵なブログのエントリ

編集:
私は私の答えを強化したいと思います:

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

  1. 続行する必要がある特定のタイムスパンがわかっているため、待機しています(使用Thread.SleepSystem.Threading.Timerまたは同様)

  2. 条件が変わるのでお待ちしています...キーワードはしばらくです!条件チェックがコードドメイン内にある場合は、WaitHandlesを使用する必要があります。それ以外の場合、外部コンポーネントは何らかのフックを提供する必要があります...デザインが悪い場合は!

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


29
今日のハードウェアを考えると、1 MBのメモリを大きな無駄とは言いません
デフォルトの

14
@デフォルトねえ、元の作者とそれについて話し合ってください:)そして、それは常にあなたのコードに依存します-またはそれ以上:要因...そして主な問題は「スレッドは限られたリソースです」-今日の子供たちは多くを知りません「ハードウェアは安い」ため、特定の実装の効率とコストについて...しかし、場合によっては非常に最適なコードを作成する必要があります
Andreas Niedermair

11
「これにより、待機中のスレッドが無駄になります」一部のプロトコル仕様が続行する前に1秒間の休止を要求する場合、何が1秒間待機しますか?一部のスレッドは、どこかで待たなければならないでしょう!スレッドは他の理由でとにかく上げる必要があり、スレッドはプロセスの存続期間中実行されるため、スレッドの作成/破棄のオーバーヘッドは多くの場合無関係です。仕様に「ポンプの電源を入れた後、圧力が安定するまで少なくとも10秒間待ってから、フィードバルブを開く」と記載されている場合、コンテキストスイッチを回避する方法を知りたいと思います。
マーティンジェームズ

9
@CodyGray-もう一度投稿を読みます。コメントにどんな色の魚も見当たりません。AndreasはWebから撤退しました: 'Thread.Sleepはそれを使用しています:MTAスレッドでテスト/デバッグしている間に長い操作をシミュレートします。.NETでは、それを使用する他の理由はありません。私は、sleep()呼び出しが必要なだけのアプリがたくさんあると主張します。開発者のレゴン(多くの場合)が、events / condvars / semas / whateverで置き換える必要がある条件モニターとしてsleep()ループを使用するように主張する場合、それを使用する他の理由がないという主張は正当化されません'。
マーティンジェームズ

8
30年間のマルチスレッドアプリ開発(主にC ++ / Delphi / Windows)では、成果物コードでsleep(0)またはsleep(1)ループが必要になることはありませんでした。たまに、デバッグの目的でそのようなコードを押し込んだことがありますが、それが顧客に伝わることはありません。「すべてのスレッドを完全に制御できないものを書いている場合」-スレッドのマイクロ管理は、開発スタッフのマイクロ管理と同じくらい大きな間違いです。スレッド管理はOSの目的です。OSが提供するツールを使用する必要があります。
マーティンジェームズ

34

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

シナリオ2-タイミングループ:ただし、粗いタイミングメカニズム(while + Thread.Sleep)は、ブロックされたスレッドがいつ「ウェイクアップする」かを正確に知る必要がないアプリケーションの99%で完全に問題ありません*。スレッドを作成するための200kサイクルも無効です-とにかくタイミングループスレッドを作成する必要があり、200kサイクルはもう1つの大きな数値です(ファイル/ソケット/ db呼び出しを開くためのサイクル数を教えてください)。

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


ありがたいことに、結果を効率的に待つためのTaskCompletionSourceがあります。
オースティンサルガット2018年

13

私は、コーディング政治の観点からこの質問に答えたいと思います。しかし、特に9〜5人の企業プログラマ向けのツールを扱っている場合、ドキュメントを書く人は、「すべきではない」や「絶対にしない」などの言葉を使う傾向があり、「自分が何をしているのか本当に理解していない限り、これを行わないでください」という意味です。やっている理由」

C#の世界で私のお気に入りの他の2つは、「lock(this)を呼び出さない」または「GC.Collect()を呼び出さない」ように指示することです。これら2つは多くのブログや公式ドキュメントで強制的に宣言されており、IMOは完全に誤った情報です。ある程度、この誤った情報は目的を果たします。これは、代替案を完全に調査する前に、初心者が理解できないことをするのを避けますが、同時に、すべてのことを行う検索エンジンを介してREAL情報を見つけることが困難になります「どうして?」という質問に答えずに、何もしないように指示する記事を指しているようです。

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

明確に「sleep()」を呼び出すことは、締め切りが現実世界の用語で明確に定義されている多くの状況で役立ちますが、sleep()のスローを開始する前に考慮および理解する必要があるスレッドを待機および通知するためのより高度なシステムがあります。 )をコードに挿入し、コードに不要なsleep()ステートメントをスローすることは、一般的に初心者の戦術と見なされます。


5

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

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

while(inWait)Thread.Sleep(5000); 

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

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

Andreas N.が述べたように、Joe AlbahariよるThreading in C#を読んでください。


それで、あなたのコード例にあることをする代わりに何がありますか?
しんぞう2016

kuhaku-はい、いい質問です。明らかにThread.Sleep()はショートカットであり、時には大きなショートカットです。上記の例では、ポーリングループの下の関数の残りのコード "while(inWait)Thread.Sleep(5000);"は新しい関数です。その新しい関数はデリゲート(コールバック関数)であり、「inWait」フラグを設定しているものに渡し、「inWait」フラグを変更する代わりに、コールバックが呼び出されます。これは私が見つけた最も短い例でした:myelin.co.nz/notes/callbacks/cs-delegates.html
mike

私がそれを取得したことを確認するためにThread.Sleep()、別の関数でラップし、whileループでそれを呼び出すことを意味しますか?
しんぞう2016

5

スリープは、制御できない独立したプログラムが一般的に使用されるリソース(ファイルなど)を使用する場合があり、プログラムが実行時にアクセスする必要があり、リソースがこれらによって使用されている場合に使用されます。あなたのプログラムがそれを使用することをブロックされている他のプログラム。この場合、コードでリソースにアクセスする場所で、リソースへのアクセスをtry-catchに入れ(リソースにアクセスできないときに例外をキャッチするため)、これをwhileループに入れます。リソースが空いている場合、スリープは呼び出されません。しかし、リソースがブロックされている場合は、適切な時間スリープし、リソースに再度アクセスしようとします(これがループの原因です)。ただし、ループに何らかのリミッターを配置する必要があるため、無限ループになる可能性はありません。


3

ここではカバーしきれないユースケースがあり、これがThread.Sleep()を使用する正当な理由であると主張します。

クリーンアップジョブを実行するコンソールアプリケーションで、大量のかなり高価なデータベース呼び出しを、何千もの同時ユーザーが共有するDBに対して行う必要があります。DBに影響を与えず、他のユーザーを何時間も除外しないようにするには、呼び出し間で100ミリ秒程度の一時停止が必要です。これはタイミングとは関係なく、他のスレッドがDBにアクセスできるようにするだけです。

サーバー上で単一のインスタンスとして実行されるスレッドに1 MBのスタックがある場合と同様に、実行に500ミリ秒かかる呼び出し間のコンテキスト切り替えに2000〜8000サイクルを費やすことは無害です。


ええ、Thread.Sleepあなたが説明しているようなシングルスレッド、単一目的のコンソールアプリケーションで使用することは完全に問題ありません。
Theodor Zoulias

-7

私はここで多くに同意しますが、それも依存すると思います。

最近、私はこのコードを行いました:

private void animate(FlowLayoutPanel element, int start, int end)
{
    bool asc = end > start;
    element.Show();
    while (start != end) {
        start += asc ? 1 : -1;
        element.Height = start;
        Thread.Sleep(1);
    }
    if (!asc)
    {
        element.Hide();
    }
    element.Focus();
}

シンプルなアニメーション機能で、使っThread.Sleepていました。

私の結論は、もしそれが仕事をするなら、それを使うことです。


-8

シナリオ2でのThread.Sleepの使用に対する1つの有効な引数を見たことがない人のために、実際には1つあります-アプリケーション出口は、whileループによって保持されます(SCENARIO 1/3は単なる愚かなので、これ以上の価値はありません言及)

Thread.Sleepを知っていて叫んでいるふりをする人の多くは悪用しているが、実際に使用しない理由を要求した私たちにとって、単一の有効な理由について言及しなかったが、ここでは、PeteのおかげでThread.Sleepを使用している。悪です(タイマー/ハンドラーで簡単に回避できます)

    static void Main(string[] args)
    {
        Thread t = new Thread(new ThreadStart(ThreadFunc));
        t.Start();

        Console.WriteLine("Hit any key to exit.");
        Console.ReadLine();

        Console.WriteLine("App exiting");
        return;
    }

    static void ThreadFunc()
    {
        int i=0;
        try
        {
            while (true)
            {
                Console.WriteLine(Thread.CurrentThread.ThreadState.ToString() + " " + i);

                Thread.Sleep(1000 * 10);
                i++;
            }
        }
        finally
        {
            Console.WriteLine("Exiting while loop");
        }
        return;
    }

9
-いや、Thread.Sleepこれは理由ではありません(新しいスレッドと継続的なwhileループはそうです)。Thread.Sleep-line-et voilaを削除するだけです。プログラムも終了しません...
Andreas Niedermair
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.