Por que o Thread.Sleep é tão prejudicial

128

Costumo ver mencionado que Thread.Sleep();não deve ser usado, mas não consigo entender por que isso acontece. Se Thread.Sleep();puder causar problemas, existem soluções alternativas com o mesmo resultado que seriam seguras?

por exemplo.

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

outro é:

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);
    }
}
Burimi
fonte
3
Um resumo do blog pode ser 'não use mal o Thread.sleep ()'.
Martin James
5
Eu não diria que é prejudicial. Eu prefiro dizer que é como, goto:ou seja, provavelmente existe uma solução melhor para seus problemas do que Sleep.
Padrão
9
Não é exatamente o mesmo que goto, que é mais um cheiro de código do que um cheiro de design. Não há nada errado com um compilador inserindo gotos no seu código: o computador não fica confuso. Mas Thread.Sleepnão é exatamente o mesmo; compiladores não inserem essa chamada e isso tem outras consequências negativas. Mas sim, o sentimento geral de que usá-lo está errado, porque quase sempre existe uma solução melhor, certamente está correta.
Cody Gray
31
Todo mundo está opinando sobre por que os exemplos acima são ruins, mas ninguém forneceu uma versão reescrita que não usa Thread.Sleep () que ainda cumpre o objetivo dos exemplos fornecidos.
precisa saber é o seguinte
3
Toda vez que você colocar sleep()em código (ou testes) um filhote de cachorro morre
Reza S

Respostas:

163

Os problemas com a chamada Thread.Sleepsão explicados de maneira bastante sucinta aqui :

Thread.Sleeptem seu uso: simulando operações demoradas ao testar / depurar em um thread do MTA. No .NET não há outro motivo para usá-lo.

Thread.Sleep(n)significa bloquear o encadeamento atual por pelo menos o número de fatias de tempo (ou quantum de encadeamentos) que podem ocorrer em n milissegundos. A duração de uma divisão do tempo é diferente em diferentes versões / tipos de Windows e processadores diferentes e geralmente varia de 15 a 30 milissegundos. Isso significa que é quase garantido que o encadeamento bloqueie por mais de nmilissegundos. A probabilidade de seu encadeamento ser reativado exatamente após nmilissegundos é quase tão impossível quanto possível. Portanto, não Thread.Sleepfaz sentido o tempo .

Os threads são um recurso limitado, eles levam aproximadamente 200.000 ciclos para criar e cerca de 100.000 ciclos para destruir. Por padrão, eles reservam 1 megabyte de memória virtual para sua pilha e usam de 2 a 8 mil ciclos para cada alternância de contexto. Isso torna qualquer thread em espera um enorme desperdício.

A solução preferida: WaitHandles

O erro mais cometido é usar Thread.Sleepcom uma construção while ( demonstração e resposta , boa entrada de blog )

EDIT:
Gostaria de melhorar minha resposta:

Temos 2 casos de uso diferentes:

  1. Estamos esperando, porque sabemos que um período de tempo específico quando devemos continuar (de uso Thread.Sleep, System.Threading.Timerou alikes)

  2. Estamos aguardando porque alguma condição muda há algum tempo ... as palavras-chave são / há algum tempo ! se a verificação de condição estiver em nosso domínio de código, devemos usar WaitHandles - caso contrário, o componente externo deve fornecer algum tipo de gancho ... se não, seu design é ruim!

Minha resposta abrange principalmente o caso de uso 2

Andreas Niedermair
fonte
29
Eu não diria que 1 MB de memória um enorme desperdício considerando hoje hardware
Padrão
14
@Default hey, discuta isso com o autor original :) e, sempre depende do seu código - ou melhor: o fator ... e a questão principal é "Os threads são um recurso limitado" - as crianças de hoje não sabem muito sobre eficiência e custo de certas implementações, porque "o hardware é barato" ... mas às vezes você precisa codificar muito melhor
Andreas Niedermair
11
'Isso torna qualquer thread em espera um enorme desperdício', eh? Se alguma especificação de protocolo exigir uma pausa de um segundo antes de continuar, o que vai esperar por 1 segundo? Algum fio, em algum lugar, terá que esperar! A sobrecarga para criação / destruição de encadeamentos geralmente é irrelevante porque um encadeamento precisa ser aumentado de qualquer maneira por outros motivos e é executado durante toda a vida útil do processo. Eu estaria interessado em ver alguma maneira de evitar interruptores de contexto quando uma especificação diz 'depois de ligar a bomba, aguarde pelo menos dez segundos para que a pressão se estabilize antes de abrir a válvula de alimentação'.
Martin James
9
@CodyGray - eu li o post novamente. Não vejo peixes de qualquer cor nos meus comentários. Andreas retirou-se da web: 'Thread.Sleep tem seu uso: simulando operações demoradas ao testar / depurar em um thread MTA. No .NET não há outro motivo para usá-lo '. Argumento que existem muitos aplicativos em que uma chamada sleep () é, exatamente, o que é necessário. Se leigons de desenvolvedores (pois existem muitos) insistirem em usar loops sleep () como monitores de condição que devem ser substituídos por eventos / condvars / semas / qualquer que seja, isso não é justificativa para uma afirmação de que 'não há outro motivo para usá-lo '
Martin James
8
Em 30 anos de desenvolvimento de aplicativos multiThreaded (principalmente C ++ / Delphi / Windows), nunca vi necessidade de ciclos de suspensão (0) ou suspensão (1) em qualquer código de entrega. Ocasionalmente, introduzi esse código para fins de depuração, mas ele nunca chegou ao cliente. 'se você estiver escrevendo algo que não controla completamente todos os threads' - o microgerenciamento de threads é um erro tão grande quanto o microgerenciamento da equipe de desenvolvimento. O gerenciamento de threads é para o que o sistema operacional existe - as ferramentas que ele fornece devem ser usadas.
Martin James
34

CENÁRIO 1 - aguarde a conclusão da tarefa assíncrona: concordo que WaitHandle / Auto | ManualResetEvent deve ser usado no cenário em que um thread aguarda a conclusão da tarefa em outro thread.

CENÁRIO 2 - temporização enquanto loop: No entanto, como um mecanismo de temporização bruto (while + Thread.Sleep) é perfeitamente adequado para 99% dos aplicativos que NÃO requerem saber exatamente quando o Thread bloqueado deve "acordar *. O argumento necessário" Os ciclos de 200k para criar o encadeamento também são inválidos - o encadeamento do loop de sincronização precisa ser criado de qualquer maneira e os ciclos de 200k são apenas outro grande número (diga-me quantos ciclos para abrir uma chamada de arquivo / soquete / db?).

Então, se enquanto o + Thread.Sleep funciona, por que complicar as coisas? Somente advogados de sintaxe seriam práticos !

Swab.Jat
fonte
Felizmente, agora temos o TaskCompletionSource para aguardar com eficiência um resultado.
Austin Salgat 27/03/19
13

Gostaria de responder a essa pergunta de uma perspectiva da política de codificação, que pode ou não ser útil a alguém. Mas, particularmente quando você lida com ferramentas destinadas a programadores corporativos de 9 a 5, as pessoas que escrevem documentação tendem a usar palavras como "não deveriam" e "nunca" querem dizer "não fazem isso a menos que você realmente saiba o que quer." estamos fazendo e por que ".

Alguns dos meus outros favoritos no mundo C # são que eles dizem para você "nunca chamar lock (this)" ou "nunca chamar GC.Collect ()". Esses dois são declarados à força em muitos blogs e documentação oficial, e a IMO é desinformação completa. Em certo nível, essa desinformação serve a seu propósito, pois impede os iniciantes de fazer coisas que não entendem antes de pesquisar completamente as alternativas, mas, ao mesmo tempo, dificulta a obtenção de informações reais através de mecanismos de pesquisa que todos parecem apontar para artigos dizendo para você não fazer algo enquanto não oferece resposta à pergunta "por que não?"

Politicamente, tudo se resume ao que as pessoas consideram "bom design" ou "mau design". A documentação oficial não deve ditar o design do meu aplicativo. Se houver realmente uma razão técnica para você não chamar sleep (), a IMO deve informar a documentação de que é totalmente aceitável chamá-la em cenários específicos, mas talvez ofereça algumas soluções alternativas que sejam independentes de cenário ou mais apropriadas para o outro cenários.

Chamar claramente "sleep ()" é útil em muitas situações em que os prazos são claramente definidos em termos de tempo real, no entanto, existem sistemas mais sofisticados para aguardar e sinalizar threads que devem ser considerados e entendidos antes de você começar a dormir ( ) em seu código, e jogar instruções sleep () desnecessárias em seu código é geralmente considerada uma tática para iniciantes.

Jason Nelson
fonte
5

É o loop 1) .spinning e 2) .polling dos seus exemplos que as pessoas advertem , não a parte Thread.Sleep (). Eu acho que Thread.Sleep () geralmente é adicionado para melhorar facilmente o código que está girando ou em um loop de pesquisa, por isso está associado apenas ao código "ruim".

Além disso, as pessoas fazem coisas como:

while(inWait)Thread.Sleep(5000); 

onde a variável inWait não é acessada de maneira segura para threads, o que também causa problemas.

O que os programadores desejam ver são os threads controlados pelas construções Events, Signaling and Locking e, quando você faz isso, não precisa de Thread.Sleep (), e as preocupações com o acesso variável seguro por thread também são eliminadas. Como exemplo, você pode criar um manipulador de eventos associado à classe FileSystemWatcher e usar um evento para acionar seu segundo exemplo em vez de fazer um loop?

Como Andreas N. mencionou, leia Threading in C #, de Joe Albahari , é realmente muito bom.

Mike
fonte
Então, qual é a alternativa para fazer o que está no seu exemplo de código?
Shinzou 15/04
kuhaku - Sim, boa pergunta. Obviamente Thread.Sleep () é um atalho e, às vezes, um grande atalho. Para o exemplo acima, o restante do código na função abaixo do loop de sondagem "while (inWait) Thread.Sleep (5000);" é uma nova função. Essa nova função é um delegado (função de retorno de chamada) e você a transmite para o que estiver configurando o sinalizador "inWait" e, em vez de alterar o sinalizador "inWait", o retorno de chamada é invocado. Este foi o exemplo mais curto que pude encontrar: myelin.co.nz/notes/callbacks/cs-delegates.html
mike
Só para ter certeza de que eu entendi, você pretende encerrar Thread.Sleep()com outra função e chamar isso no loop while?
Shinzou 16/04/19
5

A suspensão é usada nos casos em que programas independentes sobre os quais você não tem controle podem às vezes usar um recurso comumente usado (por exemplo, um arquivo), que seu programa precisa acessar quando é executado e quando o recurso está em uso por esses outros programas que seu programa está impedido de usá-lo. Nesse caso, em que você acessa o recurso no seu código, coloca o acesso ao recurso em uma tentativa de captura (para capturar a exceção quando não é possível acessar o recurso) e o coloca em um loop while. Se o recurso estiver livre, o sono nunca será chamado. Mas se o recurso estiver bloqueado, você dorme por um período de tempo apropriado e tenta acessar o recurso novamente (é por isso que está fazendo um loop). No entanto, lembre-se de que você deve colocar algum tipo de limitador no loop, para que não seja um loop potencialmente infinito.

Steve Greene
fonte
3

Eu tenho um caso de uso que ainda não vejo abordado aqui e argumentarei que esse é um motivo válido para usar Thread.Sleep ():

Em um aplicativo de console executando tarefas de limpeza, preciso fazer uma grande quantidade de chamadas de banco de dados bastante caras para um banco de dados compartilhado por milhares de usuários simultâneos. Para não martelar o banco de dados e excluir outros por horas, precisarei de uma pausa entre as chamadas, na ordem de 100 ms. Isso não está relacionado ao tempo, apenas para fornecer acesso ao banco de dados para outros threads.

Gastar 2000-8000 ciclos na alternância de contexto entre chamadas que podem levar 500 ms para executar é benigno, assim como ter 1 MB de pilha para o encadeamento, que é executado como uma única instância em um servidor.

Henrik R Clausen
fonte
Sim, usar Thread.Sleepem um aplicativo de console de thread único e de propósito único como o que você descreve está perfeitamente bem.
Theodor Zoulias
-7

Eu concordo com muitos aqui, mas também acho que depende.

Recentemente eu fiz este código:

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

Era uma função animada simples, e eu a usei Thread.Sleep.

Minha conclusão, se funcionar, use-o.


fonte
-8

Para aqueles que não viram um argumento válido contra o uso do Thread.Sleep no SCENARIO 2, existe realmente um - a saída do aplicativo é mantida pelo loop while (o SCENARIO 1/3 é simplesmente estúpido, portanto não merece mais mencionando)

Muitos que fingem estar gritando Thread.Sleep é malvado, não mencionou uma única razão válida para aqueles de nós que exigiram uma razão prática para não usá-lo - mas aqui está, graças a Pete - Thread.Sleep é mau (pode ser facilmente evitado com um temporizador / manipulador)

    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;
    }
Swab.Jat
fonte
9
-, não, Thread.Sleepnão é a razão para isso (o novo thread e o contínuo while-loop)! você pode simplesmente remover o Thread.Sleep-line - et voila: o programa não sairá bem ...
Andreas Niedermair