A classe CancellationTokenSource
é descartável. Uma rápida olhada no Reflector comprova o uso de KernelEvent
, um recurso (provavelmente) não gerenciado. Como CancellationTokenSource
não possui finalizador, se não descartá-lo, o GC não o fará.
Por outro lado, se você observar os exemplos listados no artigo do MSDN Cancelamento em threads gerenciados , apenas um trecho de código descartará o token.
Qual é a maneira correta de descartá-lo em código?
- Você não pode quebrar o código iniciando sua tarefa paralela
using
se não o esperar. E faz sentido ter cancelamento apenas se você não esperar. - Claro que você pode adicionar
ContinueWith
tarefas com umaDispose
ligação, mas é esse o caminho a seguir? - E as consultas canceláveis do PLINQ, que não são sincronizadas novamente, mas apenas fazem algo no final? Vamos dizer
.ForAll(x => Console.Write(x))
? - É reutilizável? O mesmo token pode ser usado para várias chamadas e depois descartá-lo com o componente host, digamos, controle da interface do usuário?
Como ele não possui algo como um Reset
método para limpeza IsCancelRequested
e Token
campo, eu suponho que não seja reutilizável; portanto, toda vez que você inicia uma tarefa (ou uma consulta PLINQ), deve criar uma nova. É verdade? Se sim, minha pergunta é qual é a estratégia correta e recomendada para lidar com Dispose
essas muitas CancellationTokenSource
instâncias?
fonte
Important: The CancellationTokenSource class implements the IDisposable interface. You should be sure to call the CancellationTokenSource.Dispose method when you have finished using the cancellation token source to free any unmanaged resources it holds.
- docs.microsoft.com/en-us/dotnet/standard/threading/…Não achei que nenhuma das respostas atuais fosse satisfatória. Depois de pesquisar, encontrei esta resposta de Stephen Toub ( referência ):
A parte ousada que eu acho que é a parte importante. Ele usa "mais impactante", o que deixa um pouco vago. Estou interpretando como significando que a chamada
Dispose
nessas situações deve ser feita, caso contrário, o usoDispose
não é necessário.fonte
Dei uma olhada no ILSpy,
CancellationTokenSource
mas só consigo descobrirm_KernelEvent
qual é realmente umManualResetEvent
, que é uma classe de wrapper para umWaitHandle
objeto. Isso deve ser tratado adequadamente pelo GC.fonte
Você deve sempre descartar
CancellationTokenSource
.Como descartá-lo depende exatamente do cenário. Você propõe vários cenários diferentes.
using
só funciona quando você está usandoCancellationTokenSource
algum trabalho paralelo que está esperando. Se esse é o seu senario, então ótimo, é o método mais fácil.Ao usar tarefas, use uma
ContinueWith
tarefa conforme indicado para descartarCancellationTokenSource
.Para o plinq, você pode usá-
using
lo desde que o esteja executando em paralelo, mas aguardando a conclusão de todos os trabalhadores em execução paralelo.Para a interface do usuário, você pode criar um novo
CancellationTokenSource
para cada operação cancelável que não esteja vinculada a um único gatilho de cancelamento. Mantenha aeList<IDisposable>
adicione cada fonte à lista, descartando todas quando o componente for descartado.Para threads, crie um novo thread que junte todos os threads de trabalho e feche a fonte única quando todos os threads de trabalho forem concluídos. Consulte CancellationTokenSource, quando descartar?
Sempre tem um jeito.
IDisposable
instâncias devem sempre ser descartadas. As amostras geralmente não o fazem porque são amostras rápidas para mostrar o uso principal ou porque adicionar todos os aspectos da classe que está sendo demonstrada seria muito complexo para uma amostra. A amostra é apenas uma amostra, não necessariamente (ou mesmo geralmente) código de qualidade da produção. Nem todas as amostras são aceitáveis para serem copiadas no código de produção como estão.fonte
await
a tarefa e dispor o CancellationTokenSource no código que vem depois da espera?await
uma operação, você poderá retomar devido a umOperationCanceledException
. Você pode então ligarDispose()
. Porém, se ainda houver operações em execução e usando o correspondenteCancellationToken
, esse token ainda será relatadoCanBeCanceled
como sendotrue
a fonte descartada. Se eles tentarem registrar um retorno de chamada de cancelamento, BOOM! ,ObjectDisposedException
. É suficientemente seguro ligarDispose()
após a conclusão bem-sucedida da (s) operação (ões). Fica realmente complicado quando você realmente precisa cancelar algo.Essa resposta ainda está aparecendo nas pesquisas do Google e acredito que a resposta votada não fornece a história completa. Depois de olhar sobre o código-fonte para
CancellationTokenSource
(CTS) eCancellationToken
(CT) Eu acredito que para a maioria dos casos de uso a seqüência de código a seguir é bom:O
m_kernelHandle
campo interno mencionado acima é o objeto de sincronização que suporta aWaitHandle
propriedade nas classes CTS e CT. É instanciado apenas se você acessar essa propriedade. Portanto, a menos que você esteja usandoWaitHandle
alguma sincronização de threads da velha escola em seuTask
descarte de chamadas, não terá efeito.Obviamente, se você o estiver usando, faça o que é sugerido pelas outras respostas acima e adie a chamada
Dispose
até que todas asWaitHandle
operações usando o identificador sejam concluídas, porque, conforme descrito na documentação da API do Windows para WaitHandle , os resultados são indefinidos.fonte
IsCancellationRequested
propriedade do token pesquisando, retornando a chamada ou identificando a espera". Em outras palavras: Pode não ser você (por exemplo, quem está fazendo a solicitação assíncrona) que usa a alça de espera, pode ser o ouvinte (por exemplo, aquele que responde à solicitação). O que significa que você, como responsável pela eliminação, efetivamente não tem controle sobre o uso ou não da alça de espera.Já faz muito tempo que eu perguntei isso e obtive muitas respostas úteis, mas me deparei com uma questão interessante relacionada a isso e pensei em publicá-la aqui como outra resposta:
Você deve ligar
CancellationTokenSource.Dispose()
apenas quando tiver certeza de que ninguém tentará obter asToken
propriedades do CTS . Caso contrário, você não deve chamá-lo, porque é uma corrida. Por exemplo, veja aqui:https://github.com/aspnet/AspNetKatana/issues/108
Na correção desse problema, o código que
cts.Cancel(); cts.Dispose();
foi feito anteriormente foi editado para ser feito,cts.Cancel();
porque qualquer um que tenha azar de tentar obter o token de cancelamento para observar seu estado de cancelamento após aDispose
chamada, infelizmente também precisará lidarObjectDisposedException
- além doOperationCanceledException
que eles estavam planejando.Outra observação importante relacionada a essa correção é feita pelo Tratcher: "O descarte é necessário apenas para tokens que não serão cancelados, pois o cancelamento faz a mesma limpeza". ou seja, apenas fazer em
Cancel()
vez de descartar é realmente bom o suficiente!fonte
Eu criei uma classe thread-safe que vincula a
CancellationTokenSource
aTask
e garante queCancellationTokenSource
ele será descartado quando o associado forTask
concluído. Ele usa bloqueios para garantir que oCancellationTokenSource
não seja cancelado durante ou após o descarte. Isso ocorre para conformidade com a documentação , que afirma:E também :
Aqui está a classe:
Os principais métodos da
CancelableExecution
classe são theRunAsync
e theCancel
. Por padrão, operações simultâneas não são permitidas, o que significa queRunAsync
a segunda chamada cancelará silenciosamente e aguardará a conclusão da operação anterior (se ainda estiver em execução), antes de iniciar a nova operação.Essa classe pode ser usada em aplicativos de qualquer tipo. Seu uso principal, porém, é em aplicativos de interface do usuário, dentro de formulários com botões para iniciar e cancelar uma operação assíncrona ou com uma caixa de listagem que cancela e reinicia uma operação toda vez que seu item selecionado é alterado. Aqui está um exemplo do primeiro caso:
O
RunAsync
método aceita um extraCancellationToken
como argumento, vinculado ao criado internamenteCancellationTokenSource
. O fornecimento desse token opcional pode ser útil em cenários avançados.fonte