Quero desencadear uma tarefa para executar em um thread em segundo plano. Não quero esperar a conclusão das tarefas.
No .net 3.5, eu teria feito isso:
ThreadPool.QueueUserWorkItem(d => { DoSomething(); });
No .net 4, o TPL é a maneira sugerida. O padrão comum que eu vi recomendado é:
Task.Factory.StartNew(() => { DoSomething(); });
No entanto, o StartNew()
método retorna um Task
objeto que implementa IDisposable
. Isso parece ser esquecido pelas pessoas que recomendam esse padrão. A documentação do MSDN sobre o Task.Dispose()
método diz:
"Sempre chame Dispose antes de liberar sua última referência para a tarefa."
Você não pode chamar a disposição em uma tarefa até que ela seja concluída, portanto, fazer com que o encadeamento principal aguarde e descarte a chamada acabaria com o ponto de execução em um encadeamento em segundo plano. Também não parece haver nenhum evento concluído / finalizado que possa ser usado para limpeza.
A página MSDN na classe Task não comenta isso, e o livro "Pro C # 2010 ..." recomenda o mesmo padrão e não faz nenhum comentário sobre o descarte de tarefas.
Eu sei que se eu deixar o finalizador irá pegá-lo no final, mas isso vai voltar e me morder quando estou fazendo muito fogo e esquecer tarefas como essa e o encadeamento do finalizador ficar sobrecarregado?
Então, minhas perguntas são:
- É aceitável não chamar
Dispose()
aTask
turma neste caso? E se sim, por que e há riscos / consequências? - Existe alguma documentação que discuta isso?
- Ou existe uma maneira apropriada de descartar o
Task
objeto que perdi? - Ou existe outra maneira de fazer fogo e esquecer tarefas com o TPL?
fonte
Respostas:
Há uma discussão sobre isso nos fóruns do MSDN .
Stephen Toub, membro da equipe Microsoft pfx, tem o seguinte a dizer:
Atualização (outubro de 2012)
Stephen Toub postou um blog intitulado Preciso descartar as tarefas? que fornece mais detalhes e explica as melhorias no .Net 4.5.
Em resumo: você não precisa descartar
Task
objetos 99% do tempo.Há dois motivos principais para descartar um objeto: liberar recursos não gerenciados de maneira determinística e oportuna e evitar o custo de executar o finalizador do objeto. Nenhum destes se aplica à
Task
maior parte do tempo:Task
aloca o identificador de espera interno (o único recurso não gerenciado noTask
objeto) é quando você usa explicitamente oIAsyncResult.AsyncWaitHandle
deTask
, eTask
objeto em si não possui um finalizador; o próprio identificador está envolvido em um objeto com um finalizador; portanto, a menos que esteja alocado, não há finalizador para executar.fonte
EndInvoke
WinFormsBeginInvoke
ao executar o código no thread da interface do usuário). (2) Stephen Toub é bastante conhecido como orador regular sobre o uso efetivo do PFX (por exemplo, no channel9.msdn.com ), portanto, se alguém pode dar uma boa orientação, então é ele. Observe seu segundo parágrafo: há momentos em que deixar as coisas para o finalizador é melhor.Esse é o mesmo tipo de problema da classe Thread. Consome 5 identificadores do sistema operacional, mas não implementa IDisposable. Boa decisão dos designers originais, é claro que existem poucas maneiras razoáveis de chamar o método Dispose (). Você teria que ligar para Join () primeiro.
A classe Task adiciona um identificador a isso, um evento de redefinição manual interna. Qual é o recurso mais barato do sistema operacional que existe. Obviamente, seu método Dispose () pode liberar apenas esse identificador de evento, não os 5 identificadores que o Thread consome. Sim, não se preocupe .
Lembre-se de que você deve estar interessado na propriedade IsFaulted da tarefa. É um tópico bastante feio, você pode ler mais sobre isso neste artigo da MSDN Library . Depois de lidar com isso corretamente, você também deve ter uma boa localização no seu código para descartar as tarefas.
fonte
Thread
na maioria dos casos, ela usa o ThreadPool.Eu adoraria ver alguém avaliar a técnica mostrada nesta postagem: Typesafe invocação assíncrona de delegado assíncrona em C #
Parece que um método de extensão simples lida com todos os casos triviais de interação com as tarefas e pode ser descartado.
fonte
Task
instância retornada porContinueWith
, mas veja a citação de Stephen Toub é a resposta aceita: não há nada a descartar se nada executar uma espera de bloqueio em uma tarefa.Task disper = null; disper = tsk.ContinueWith(cnt => { cnt.Dispose(); disper.Dispose(); });