Há casos em que é preferível usar um objeto Thread antigo e simples em vez de uma das construções mais recentes?

112

Vejo muitas pessoas em postagens de blog e aqui no SO evitando ou aconselhando contra o uso da Threadclasse em versões recentes do C # (e, claro, 4.0+, com a adição de Task& amigos). Mesmo antes, havia debates sobre o fato de que a funcionalidade de um thread simples e antigo pode ser substituída em muitos casos pela ThreadPoolclasse.

Além disso, outros mecanismos especializados estão tornando a Threadclasse menos atraente, como Timera substituição do combo feio Thread+ Sleep, enquanto para GUIs temosBackgroundWorker , etc.

Ainda assim, o Threadparece ser um conceito muito familiar para algumas pessoas (inclusive eu), pessoas que, quando confrontadas com uma tarefa que envolve algum tipo de execução paralela, passam diretamente a usar o bom e velhoThread classe. Ultimamente, tenho me perguntado se é hora de corrigir meus hábitos.

Portanto, minha pergunta é: há casos em que é necessário ou útil usar um Threadobjeto simples e antigo em vez de uma das construções acima?

tudor
fonte
4
Não é para criticar (ou talvez ... :)), mas isso é .NET (ou seja, não limitado a C #).
MetalMikester
11
Até agora, o representante médio de um usuário que responde a essa pergunta é 64.5k. Uma exibição bastante impressionante
zzzzBov
1
@zzzzBov: Eu estava pensando em postar minha própria resposta, mas agora não posso, por medo de diminuir a média :)
Brian Gideon
@BrianGideon, não vejo razão para que isso impeça você ou qualquer outra pessoa de responder a esta pergunta.
zzzzBov
@Brian Gideon: Por favor, poste. :)
Tudor

Respostas:

107

A classe Thread não pode se tornar obsoleta porque obviamente é um detalhe de implementação de todos os outros padrões que você mencionou.

Mas essa não é realmente a sua pergunta; sua pergunta é

existem casos em que é necessário ou útil usar um objeto Thread antigo e simples em vez de uma das construções acima?

Certo. Precisamente naqueles casos em que uma das construções de nível superior não atende às suas necessidades.

Meu conselho é que se você se encontrar em uma situação em que as ferramentas de abstração superior existentes não atendam às suas necessidades e deseja implementar uma solução usando threads, você deve identificar a abstração ausente de que realmente precisa e, em seguida, implementar essa abstração usando threads e, em seguida, use a abstração .

Eric Lippert
fonte
33
Todos bons conselhos. Mas você tem um exemplo em que um objeto de thread simples e antigo pode ser preferível a uma das construções mais recentes?
Robert Harvey
A depuração de várias threads com problemas de tempo para carregar algum código pode ser muito mais fácil às vezes do que usar outras construções de nível 'superior'. E você precisa do conhecimento básico de trabalhar e usar Threads de qualquer maneira.
CodingBarfield
Obrigado pela resposta, Eric. É realmente fácil esquecer, sob o recente bombardeio sobre os novos recursos de threading, que às vezes o thread antigo ainda é necessário. Bem, eu acho que o conhecimento do bare-metal é útil de qualquer maneira.
Tudor
15
@RobertHarvey: Claro. Por exemplo, se você tiver uma situação clássica de "produtor / consumidor", em que deseja ter um thread dedicado a retirar coisas de uma fila de trabalho e outro thread dedicado a colocar coisas em uma fila de trabalho. Ambos fazem loop para sempre; eles não mapeiam bem as tarefas que você faz por um tempo e depois obtém um resultado. Você não precisa de um pool de threads porque existem apenas duas threads. E você pode ser inteligente com o uso de bloqueios e sinais para garantir que a fila seja mantida segura sem bloquear o outro thread por muito tempo.
Eric Lippert,
@Eric: Essa abstração já não é oferecida pelo TPL Dataflow ?
Allon Guralnek,
52

Threads são um bloco de construção básico para certas coisas (nomeadamente paralelismo e assincronia) e, portanto, não devem ser retirados. No entanto , para a maioria das pessoas e a maioria dos casos de uso Há coisas mais apropriadas para uso que você mencionou, como pools de threads (que fornecem uma boa maneira de lidar com muitos pequenos trabalhos em paralelo, sem sobrecarregar a máquina por desova 2000 segmentos de uma vez), BackgroundWorker( que encapsula eventos úteis para um único trabalho de curta duração).

Mas só porque em muitos casos esses são mais apropriados, pois protegem o programador de reinventar a roda desnecessariamente, cometer erros estúpidos e coisas do tipo, isso não significa que a classe Thread esteja obsoleta. Ele ainda é usado pelas abstrações mencionadas acima e você ainda precisará dele se precisar de controle refinado sobre threads que não são cobertos pelas classes mais especiais.

Na mesma linha, .NETnão proíbe o uso de arrays, apesar de List<T>ser mais adequado para muitos casos em que as pessoas usam arrays. Simplesmente porque você ainda pode querer construir coisas que não são cobertas pela biblioteca padrão.

Joey
fonte
6
Só porque uma transmissão automática funciona 99% do tempo, não significa que não haja valor nas transmissões manuais, mesmo ignorando a preferência do motorista.
Guvante
4
Não estou muito familiarizado com idiomas para dirigir, visto que sou ciclista e usuário de transporte público. Mas uma transmissão manual não é o cerne de uma automática - não é uma mão mecânica movendo um manche. Não é realmente uma abstração sobre o outro, até onde posso ver.
Joey
2
Você poderia substituir a palavra "thread" por "código não gerenciado" no segundo parágrafo e ainda soará verdadeiro
Conrad Frix
1
@Joey: Oh, achei que você quisesse dizer a parte obsoleta, o valor de ter um controle refinado, ao custo da usabilidade, embora a maioria nunca precise disso. BTW tecnicamente, a parte interessante de uma transmissão manual é a embreagem de qualquer maneira.
Guvante
3
Se vocês realmente querem ir com a metáfora da transmissão, a maioria das transmissões sequenciais (como a transmissão SMG da BMW) são , na verdade, transmissões manuais com embreagem operada por computador e seletor de marcha. Eles não são sistemas de engrenagens planetárias como automáticas convencionais.
Adam Robinson
13

Taske Threadsão abstrações diferentes. Se você deseja modelar um thread, a Threadclasse ainda é a escolha mais apropriada. Por exemplo, se você precisa interagir com o tópico atual, não vejo nenhum tipo melhor para isso.

No entanto, como você observou, o .NET adicionou várias abstrações dedicadas que são preferíveis Threadem muitos casos.

Brian Rasmussen
fonte
9

A Threadclasse não está obsoleta, ainda é útil em circunstâncias especiais.

Onde eu trabalho, escrevemos um 'processador de segundo plano' como parte de um sistema de gerenciamento de conteúdo: um serviço do Windows que monitora diretórios, endereços de e-mail e feeds RSS, e toda vez que algo novo aparece, execute uma tarefa nele - normalmente para importar o dados.

As tentativas de usar o pool de threads para isso não funcionaram: ele tenta executar muitas coisas ao mesmo tempo e destruir os discos, então implementamos nosso próprio sistema de pesquisa e execução usando diretamente a Threadclasse.

MiMo
fonte
Não tenho ideia se usar Thread diretamente aqui é a solução certa aqui, mas +1 para realmente dar um exemplo em que ir para Thread era necessário.
Oddthinking
6

As novas opções tornam o uso direto e o gerenciamento dos threads (caros) menos frequentes.

pessoas que, ao se depararem com uma tarefa que envolve algum tipo de execução paralela, pulam diretamente para o uso da boa e velha classe Thread.

Que é uma maneira muito cara e relativamente complexa de fazer as coisas em paralelo.

Observe que a despesa é o mais importante: você não pode usar um thread completo para fazer um pequeno trabalho, seria contraproducente. O ThreadPool combate os custos, a classe Task as complexidades (exceções, espera e cancelamento).

Henk Holterman
fonte
5

Para responder à pergunta " existem casos em que é necessário ou útil usar um objeto Thread antigo simples ", eu diria que um Thread antigo comum é útil (mas não necessário) quando você tem um processo de longa execução que você ganhou ' Nunca interaja com um tópico diferente.

Por exemplo, se você estiver escrevendo um aplicativo que se inscreve para receber mensagens de algum tipo de fila de mensagens e seu aplicativo vai fazer mais do que apenas processar essas mensagens, seria útil usar um Thread porque o thread será independente (ou seja, você não está esperando que isso seja feito) e não é de curta duração. Usar a classe ThreadPool é mais para enfileirar um monte de itens de trabalho de curta duração e permitir que a classe ThreadPool gerencie o processamento eficiente de cada um conforme um novo Thread está disponível. Tarefas podem ser usadas onde você usaria Thread diretamente, mas no cenário acima eu não acho que eles comprariam muito para você. Eles ajudam você a interagir com o tópico mais facilmente (o que o cenário acima não

Derek Greer
fonte
Eu concordo que a maioria das coisas legais do novo paralelismo são projetadas em torno de pequenas tarefas refinadas de curto prazo, e threads de longa execução ainda devem ser implementados com System.IO.Threading.Thread.
Ben Voigt
4

Provavelmente não é a resposta que você esperava, mas eu uso Thread o tempo todo ao codificar no .NET Micro Framework. MF é bastante reduzido e não inclui abstrações de nível superior e a classe Thread é super flexível quando você precisa obter o último bit de desempenho de uma CPU de baixo MHz.

AngryHacker
fonte
Ei, esse é realmente um bom exemplo! Obrigado. Não pensei em frameworks que não suportam os novos recursos.
Tudor
3

Você pode comparar a classe Thread ao ADO.NET. Não é a ferramenta recomendada para realizar o trabalho, mas não está obsoleta. Outras ferramentas são construídas em cima dele para facilitar o trabalho.

Não é errado usar a classe Thread em vez de outras coisas, especialmente se essas coisas não fornecem a funcionalidade de que você precisa.

Kyle Trauberman
fonte
3

Não é definitivamente obsoleto.

O problema com aplicativos multithread é que eles são muito difíceis de acertar (frequentemente comportamento indeterminista, entrada, saída e também o estado interno são importantes), então um programador deve empurrar o máximo de trabalho possível para o framework / ferramentas. Abstraia-o. Mas, o inimigo mortal da abstração é a performance.

Portanto, minha pergunta é, há casos em que é necessário ou útil usar um objeto Thread antigo e simples em vez de uma das construções acima?

Eu escolheria Threads e bloqueios apenas se houvesse sérios problemas de desempenho, metas de alto desempenho.

Lukasz Madon
fonte
3

Sempre usei a classe Thread quando preciso manter a contagem e o controle sobre os threads que criei. Sei que poderia usar o threadpool para manter todos os meus trabalhos pendentes, mas nunca encontrei uma boa maneira de manter o controle de quanto trabalho está sendo feito ou qual é o seu status.

Em vez disso, crio uma coleção e coloco os threads nelas depois de girá-los - a última coisa que um thread faz é se remover da coleção. Dessa forma, sempre posso dizer quantos threads estão em execução e posso usar a coleção para perguntar a cada um o que está fazendo. Se houver um caso em que eu precise matar todos eles, normalmente você terá que definir algum tipo de sinalizador "Abortar" em seu aplicativo, esperar que cada thread perceba isso por conta própria e se encerrar - no meu caso, eu pode percorrer a coleção e emitir um Thread.Abort para cada um.

Nesse caso, não encontrei uma maneira melhor do que trabalhar diretamente com a classe Thread. Como Eric Lippert mencionou, as outras são apenas abstrações de nível superior e é apropriado trabalhar com as classes de nível inferior quando as implementações de alto nível disponíveis não atendem às suas necessidades. Assim como às vezes você precisa fazer chamadas de API do Win32 quando o .NET não atende às suas necessidades exatas, sempre haverá casos em que a classe Thread é a melhor escolha, apesar dos "avanços" recentes.

SqlRyan
fonte