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 ()을 오용하지 마십시오'일 수 있습니다.
Martin James

5
나는 그것이 해롭다 고 말하지 않을 것입니다. 나는 그것이 goto:아마도 당신의 문제에 대한 더 나은 해결책이있을 것 같다고 말하고 싶습니다 Sleep.
기본적으로

9
그것은 정확히 같은 아니에요 goto더 디자인 냄새보다 코드 냄새처럼이다. goto코드에 s를 삽입하는 컴파일러에는 문제 가 없습니다. 컴퓨터는 혼동되지 않습니다. 그러나 Thread.Sleep정확히 동일하지는 않습니다. 컴파일러는 해당 호출을 삽입하지 않으며 다른 부정적인 결과를 초래합니다. 그러나 예, 거의 항상 더 나은 해결책이 있기 때문에 그것을 사용하는 것이 잘못되었다는 일반적인 감정은 확실히 옳습니다.
Cody Gray

32
모두가 위의 예제가 왜 나쁜지에 대한 의견을 제공하고 있지만, 주어진 예제의 목표를 달성하는 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 회가 걸립니다. 기본적으로 스택을 위해 1MB의 가상 메모리를 예약하고 각 컨텍스트 스위치에 대해 2,000-8,000 사이클을 사용합니다. 이것은 대기중인 스레드를 엄청난 낭비로 만듭니다.

선호하는 솔루션 : WaitHandles

가장 많이 만든 실수는 Thread.Sleepwhile-construct ( demo and answer , nice blog-entry ) 와 함께 사용하는 것 입니다.

편집 :
내 대답을 향상시키고 싶습니다.

두 가지 사용 사례가 있습니다.

  1. 계속해야 할 특정 기간을 알고 있기 때문에 기다리고 있습니다 Thread.Sleep( System.Threading.Timer또는 유사 항목 사용).

  2. 일부 조건이 때때로 변경되기 때문에 기다리고 있습니다. 키워드는 시간이 있습니다 ! 조건 검사가 코드 도메인에 있으면 WaitHandles를 사용해야합니다. 그렇지 않으면 외부 구성 요소가 일종의 후크를 제공해야합니다. 그렇지 않으면 디자인이 나쁘지 않습니다!

내 대답은 주로 사용 사례 2를 다룹니다.


30
나는 오늘날의 하드웨어를 고려할 때 1MB의 메모리를 낭비라고 부르지 않을 것입니다
Default

14
@Default 헤이, 원저자와 논의하세요 :) 그리고 그것은 항상 당신의 코드에 달려 있습니다. effeciency 및 특정 구현 비용에 대해 "하드웨어는 저렴"... 그러나 때때로 당신은 매우 optd 코드를 필요로하기 때문에
안드레아스 Niedermair

11
'이것은 대기중인 스레드를 엄청난 낭비로 만듭니다'응? 일부 프로토콜 사양에서 계속하기 전에 1 초의 일시 중지를 요구하는 경우 1 초 동안 무엇을 기다릴까요? 어떤 스레드는 어딘가에서 기다려야 할 것입니다! 스레드 생성 / 소멸에 대한 오버 헤드는 다른 이유로 인해 스레드가 발생해야하고 프로세스의 수명 동안 실행되기 때문에 종종 관련이 없습니다. 사양에 '펌프를 켠 후 공급 밸브를 열기 전에 압력이 안정 될 때까지 10 초 이상 기다리십시오'라는 사양이있을 때 컨텍스트 전환을 피하는 방법을보고 싶습니다.
Martin James

9
@CodyGray-게시물을 다시 읽었습니다. 내 댓글에 어떤 색깔의 물고기도 보이지 않습니다. Andreas는 웹에서 다음과 같이 말했습니다. 'Thread.Sleep은 MTA 스레드에서 테스트 / 디버깅하는 동안 긴 작업을 시뮬레이션합니다. .NET에서는 다른 이유가 없습니다. ' 나는 sleep () 호출이 필요한 많은 앱이 있다고 주장합니다. 개발자의 leigons (많은 경우)가 이벤트 / condvars / semas / whatever로 대체해야하는 조건 모니터로 sleep () 루프를 사용한다고 주장한다면 '사용할 다른 이유가 없다는 주장에 대한 정당화가 아닙니다. '.
Martin James

8
30 년 동안의 멀티 스레드 앱 개발 (대부분 C ++ / Delphi / Windows)에서, 전달 가능한 코드에서 sleep (0) 또는 sleep (1) 루프에 대한 필요성을 본 적이 없습니다. 때때로 디버깅 목적으로 이러한 코드를 삽입했지만 고객에게 전달되지는 않았습니다. '모든 스레드를 완전히 제어하지 않는 무언가를 작성하는 경우'-스레드의 마이크로 관리는 개발 직원의 마이크로 관리만큼 큰 실수입니다. 스레드 관리는 OS가 제공하는 것입니다. 제공하는 도구를 사용해야합니다.
Martin James

34

시나리오 1-비동기 작업 완료 대기 : 스레드가 다른 스레드의 작업이 완료되기를 기다리는 시나리오에서 WaitHandle / Auto | ManualResetEvent를 사용해야한다는 데 동의합니다.

시나리오 2-while 루프 타이밍 : 그러나 조잡한 타이밍 메커니즘 (while + Thread.Sleep)은 차단 된 스레드가 "깨어나야하는 *" 정확한 시기를 알 필요가없는 99 %의 응용 프로그램에 완벽하게 적합합니다 . 스레드를 생성하기위한 200k 주기도 유효하지 않습니다. 어쨌든 타이밍 루프 스레드를 생성해야하고 200k주기는 또 다른 큰 숫자입니다 (파일 / 소켓 / db 호출을 여는주기가 몇 번입니까?).

while + Thread.Sleep이 작동한다면 왜 복잡할까요? 구문 변호사 만이 실용적 일 것입니다 !


고맙게도 이제 효율적으로 결과를 기다리는 TaskCompletionSource가 있습니다.
Austin Salgat

14

누구에게나 도움이 될 수도 있고 아닐 수도있는 코딩 정치적인 관점에서이 질문에 답하고 싶습니다. 그러나 특히 9-5 명의 기업 프로그래머를 대상으로하는 도구를 다룰 때 문서를 작성하는 사람들은 "안된다"및 "절대"와 같은 단어를 사용하는 경향이 있습니다. '하고 왜'.

C # 세계에서 제가 가장 좋아하는 몇 가지는 "lock (this) 호출 안 함"또는 "GC.Collect () 호출 안 함"이라고 말합니다. 이 두 가지는 많은 블로그와 공식 문서에서 강력하게 선언되었으며 IMO는 완전히 잘못된 정보입니다. 어떤 수준에서는 이러한 잘못된 정보가 그 목적에 부합합니다. 초보자가 대안을 완전히 연구하기 전에 이해하지 못하는 일을하지 않도록하면서 동시에 검색 엔진을 통해 실제 정보를 찾기 어렵게 만듭니다. "왜 안 되는가?"라는 질문에 대한 답을 제공하지 않는 동안 아무것도하지 말라고 말하는 기사를 가리키는 것 같습니다.

정치적으로, 그것은 사람들이 "좋은 디자인"또는 "나쁜 디자인"이라고 생각하는 것으로 귀결됩니다. 공식 문서가 내 응용 프로그램의 디자인을 지시해서는 안됩니다. sleep ()을 호출하지 않아야하는 진정한 기술적 이유가 있다면 IMO 문서는 특정 시나리오에서 호출해도 괜찮다고 명시해야하지만 시나리오 독립적이거나 다른 것에 더 적합한 대체 솔루션을 제공 할 수도 있습니다. 시나리오.

"sleep ()"을 명확하게 호출하는 것은 데드 라인이 실제 시간 용어로 명확하게 정의 된 많은 상황에서 유용하지만 sleep ()을 던지기 전에 고려하고 이해해야하는 대기 및 신호 스레드를위한 더 정교한 시스템이 있습니다. )를 코드에 추가하고 코드에 불필요한 sleep () 문을 던지는 것은 일반적으로 초보자의 전술로 간주됩니다.


5

사람들이주의하는 예제의 1) .spinning 및 2) .polling 루프 는 Thread.Sleep () 부분이 아닙니다. Thread.Sleep ()은 일반적으로 회전하거나 폴링 루프에있는 코드를 쉽게 개선하기 위해 추가되므로 "나쁜"코드와 관련이 있습니다.

또한 사람들은 다음과 같은 작업을 수행합니다.

while(inWait)Thread.Sleep(5000); 

여기서 변수 inWait가 스레드로부터 안전한 방식으로 액세스되지 않아 문제가 발생합니다.

프로그래머가보고 싶은 것은 이벤트 및 시그널링 및 잠금 구조에 의해 제어되는 스레드이며, 그렇게하면 Thread.Sleep ()이 필요하지 않으며 스레드로부터 안전한 변수 액세스에 대한 우려도 제거됩니다. 예를 들어, FileSystemWatcher 클래스와 관련된 이벤트 처리기를 만들고 이벤트를 사용하여 루프 대신 두 번째 예제를 트리거 할 수 있습니까?

Andreas N.이 언급했듯이 Joe Albahari의 Threading in C #을 읽으면 정말 좋습니다.


그렇다면 코드 예제에서 수행하는 대안은 무엇입니까?
shinzou 2016-04-15

kuhaku-네, 좋은 질문입니다. 분명히 Thread.Sleep ()은 바로 가기이고 때로는 큰 바로 가기입니다. 위의 예에서 폴링 루프 "while (inWait) Thread.Sleep (5000);"아래 함수의 나머지 코드는 새 함수입니다. 새 함수는 델리게이트 (콜백 함수)이며 "inWait"플래그를 설정하는 모든 것에 전달하고 "inWait"플래그를 변경하는 대신 콜백이 호출됩니다. 이것이 제가 찾을 수있는 가장 짧은 예였습니다. myelin.co.nz/notes/callbacks/cs-delegates.html
mike

내가 그것을 얻었는지 확인하기 위해 Thread.Sleep()다른 함수 로 래핑 하고 while 루프에서 호출한다는 의미 입니까?
shinzou

5

Sleep은 사용자가 제어 할 수없는 독립 프로그램이 종종 일반적으로 사용되는 리소스 (예 : 파일)를 사용할 수 있고, 프로그램이 실행될 때 액세스해야하는 경우, 리소스가 이러한 리소스에서 사용 중일 때 사용됩니다. 다른 프로그램에서 사용이 차단됩니다. 이 경우 코드의 리소스에 액세스하는 경우 리소스 액세스를 try-catch (리소스에 액세스 할 수없는 경우 예외를 포착하기 위해)에 넣고이를 while 루프에 넣습니다. 리소스가 사용 가능한 경우 절전 모드가 호출되지 않습니다. 그러나 리소스가 차단되면 적절한 시간 동안 잠자고 리소스에 다시 액세스하려고 시도합니다 (이로 인해 루프가 발생합니다). 그러나 루프에 어떤 종류의 리미터를 넣어야하므로 잠재적으로 무한 루프가 아닙니다.


3

여기에서 다루지 않은 사용 사례가 있으며 이것이 Thread.Sleep ()을 사용하는 유효한 이유라고 주장 할 것입니다.

정리 작업을 실행하는 콘솔 응용 프로그램에서 수천 명의 동시 사용자가 공유하는 DB에 대해 상당히 비싼 데이터베이스 호출을 많이해야합니다. DB를 망치지 않고 몇 시간 동안 다른 사람을 제외하려면 호출 사이에 100ms 정도의 일시 중지가 필요합니다. 이것은 타이밍과 관련이 없으며 다른 스레드를 위해 DB에 액세스하는 것과 관련이 있습니다.

서버에서 단일 인스턴스로 실행되는 스레드에 대해 1MB의 스택이있는 것처럼 실행하는 데 500ms가 걸릴 수있는 호출 간의 컨텍스트 전환에 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

SCENARIO 2에서 Thread.Sleep 사용에 대한 유효한 주장을 한 가지도 보지 못한 분들을 위해 실제로 한 가지가 있습니다. 언급)

하지만 여기있다 피트 덕분에 - - Thread.sleep를 그것을 사용하지 않는 실제 이유를 요구하는 사람들을위한 언급 한 타당한 이유에 실패 악 비명에 - 더 - 아는 척 많은 사람들 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프로그램이 ...뿐만 아니라 종료되지 않습니다 등 짜잔 - - 라인
안드레아스 Niedermair
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.