Como usar a propriedade CancelamentoToken?

118

Comparado ao código anterior para a classe RulyCanceler , eu queria executar o código usando CancellationTokenSource.

Como faço para usá-lo conforme mencionado em Tokens de cancelamento , ou seja, sem lançar / capturar uma exceção? Posso usar a IsCancellationRequestedpropriedade?

Tentei usá-lo assim:

cancelToken.ThrowIfCancellationRequested();

e

try
{
  new Thread(() => Work(cancelSource.Token)).Start();
}
catch (OperationCanceledException)
{
  Console.WriteLine("Canceled!");
}

mas isso gerou um erro de tempo de execução cancelToken.ThrowIfCancellationRequested();no método Work(CancellationToken cancelToken):

System.OperationCanceledException was unhandled
  Message=The operation was canceled.
  Source=mscorlib
  StackTrace:
       at System.Threading.CancellationToken.ThrowIfCancellationRequested()
       at _7CancellationTokens.Token.Work(CancellationToken cancelToken) in C:\xxx\Token.cs:line 33
       at _7CancellationTokens.Token.<>c__DisplayClass1.<Main>b__0() in C:\xxx\Token.cs:line 22
       at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart()
  InnerException:

O código que executei com sucesso capturou a OperationCanceledException no novo thread:

using System;
using System.Threading;
namespace _7CancellationTokens
{
  internal class Token
  {
    private static void Main()
    {
      var cancelSource = new CancellationTokenSource();
      new Thread(() =>
      {
         try
         {
           Work(cancelSource.Token); //).Start();
         }
         catch (OperationCanceledException)
         {
            Console.WriteLine("Canceled!");
         }
         }).Start();

      Thread.Sleep(1000);
      cancelSource.Cancel(); // Safely cancel worker.
      Console.ReadLine();
    }
    private static void Work(CancellationToken cancelToken)
    {
      while (true)
      {
        Console.Write("345");
        cancelToken.ThrowIfCancellationRequested();
      }
    }
  }
}
Fulproof
fonte
2
docs.microsoft.com/en-us/dotnet/standard/threading/… tem alguns bons exemplos de uso CancellationTokenSourcecom métodos assíncronos, métodos de longa execução com pesquisa e retorno de chamada.
Ehtesh Choudhury
Este artigo mostra as opções que você tem e precisa para lidar com o token de acordo com seu caso específico.
Ognyan Dimitrov

Respostas:

140

Você pode implementar seu método de trabalho da seguinte maneira:

private static void Work(CancellationToken cancelToken)
{
    while (true)
    {
        if(cancelToken.IsCancellationRequested)
        {
            return;
        }
        Console.Write("345");
    }
}

É isso aí. Você sempre precisa lidar com o cancelamento sozinho - saia do método quando for o momento apropriado para sair (de modo que seu trabalho e dados estejam em um estado consistente)

ATUALIZAÇÃO: eu prefiro não escrever while (!cancelToken.IsCancellationRequested)porque geralmente existem poucos pontos de saída onde você pode parar a execução com segurança no corpo do loop, e o loop geralmente tem alguma condição lógica para sair (iterar sobre todos os itens da coleção, etc.). Então eu acredito que é melhor não misturar essas condições, pois elas têm intenções diferentes.

Nota de advertência sobre como evitar CancellationToken.ThrowIfCancellationRequested():

Comentário em questão por Eamon Nerbonne :

... substituindo ThrowIfCancellationRequestedcom um monte de cheques para IsCancellationRequestedsaídas normalmente, como diz esta resposta. Mas isso não é apenas um detalhe de implementação; que afeta o comportamento observável: a tarefa não terminará mais no estado cancelado, mas em RanToCompletion. E isso pode afetar não apenas verificações explícitas de estado, mas também, mais sutilmente, o encadeamento de tarefas com ContinueWith, por exemplo , dependendo do TaskContinuationOptionsusado. Eu diria que evitar ThrowIfCancellationRequestedé um conselho perigoso.

Sasha
fonte
1
Obrigado! Isso não decorre do texto online, bastante confiável (livro "C # 4.0 in a Nutshell"?) Que citei. Você poderia me dar uma referência sobre "sempre"?
Fulproof
1
Isso vem da prática e da experiência =). Não me lembro de onde sei disso. Usei "você sempre precisa" porque você realmente pode interromper o thread de trabalho com exceção de fora usando Thread.Abort (), mas isso é uma prática muito ruim. A propósito, usar o CancelamentoToken.ThrowIfCancellationRequested () também é "manipular o cancelamento você mesmo", apenas a outra maneira de fazê-lo.
Sasha
1
@OleksandrPshenychnyy Eu quis dizer substituir while (true) por while (! CancelToken.IsCancellationRequested). Isso foi útil! Obrigado!
Doug Dawson
1
@Fulproof Não há uma maneira genérica de um tempo de execução cancelar a execução do código porque os tempos de execução não são inteligentes o suficiente para saber onde um processo pode ser interrompido. Em alguns casos, é possível simplesmente sair de um loop; em outros casos, é necessária uma lógica mais complexa, ou seja, as transações devem ser revertidas, os recursos devem ser liberados (por exemplo, identificadores de arquivo ou conexões de rede). É por isso que não existe uma maneira mágica de cancelar uma tarefa sem ter que escrever algum código. O que você pensa é como encerrar um processo, mas isso não é cancelado. Essa é uma das piores coisas que podem acontecer a um aplicativo porque não pode ser limpo.
user3285954
1
@kosist Você pode usar o CancelToken.None se não planeja cancelar a operação que está iniciando manualmente. É claro que, quando o processo do sistema é eliminado, tudo é interrompido e o CancelamentoToken não tem nada a ver com isso. Portanto, sim, você só deve criar CancelamentoTokenSource se realmente precisar usá-lo para cancelar a operação. Não faz sentido criar algo que você não usa.
Sasha
26

@ BrainSlugs83

Você não deve confiar cegamente em tudo postado no stackoverflow. O comentário no código Jens está incorreto, o parâmetro não controla se as exceções são lançadas ou não.

O MSDN é muito claro sobre o que esse parâmetro controla, você leu? http://msdn.microsoft.com/en-us/library/dd321703(v=vs.110).aspx

Se throwOnFirstExceptionfor verdadeiro, uma exceção será propagada imediatamente para fora da chamada para Cancelar, evitando que os retornos de chamada restantes e as operações canceláveis ​​sejam processadas. Se throwOnFirstExceptionfor falso, essa sobrecarga agregará quaisquer exceções lançadas em um AggregateException, de forma que um retorno de chamada que lança uma exceção não evite que outros retornos de chamada registrados sejam executados.

O nome da variável também está errado porque Cancel é chamado CancellationTokenSourcenão no token em si e a origem muda o estado de cada token que gerencia.

user3285954
fonte
Também dê uma olhada na documentação (TAP) aqui sobre o uso proposto do token de cancelamento: docs.microsoft.com/en-us/dotnet/standard/…
Epstone
1
Esta é uma informação muito útil, mas não responde de forma alguma à pergunta feita.
11nallan11
16

Você pode criar uma Tarefa com token de cancelamento, ao aplicar o goto background, você pode cancelar esse token.

Você pode fazer isso em PCL https://developer.xamarin.com/guides/xamarin-forms/application-fundamentals/app-lifecycle

var cancelToken = new CancellationTokenSource();
Task.Factory.StartNew(async () => {
    await Task.Delay(10000);
    // call web API
}, cancelToken.Token);

//this stops the Task:
cancelToken.Cancel(false);

Outra solução é o Timer do usuário em Xamarin.Forms, pare o cronômetro quando o aplicativo for para o fundo https://xamarinhelp.com/xamarin-forms-timer/

Jesse Jiang
fonte
10

Você pode usar ThrowIfCancellationRequestedsem lidar com a exceção!

O uso de ThrowIfCancellationRequesteddestina-se a ser usado de dentro de a Task(não a Thread). Quando usado em a Task, você não precisa tratar a exceção sozinho (e obter o erro de exceção não tratada). Isso resultará no abandono do Taske a Task.IsCancelledpropriedade será True. Nenhuma manipulação de exceção necessária.

No seu caso específico, altere o Threadpara a Task.

Task t = null;
try
{
    t = Task.Run(() => Work(cancelSource.Token), cancelSource.Token);
}

if (t.IsCancelled)
{
    Console.WriteLine("Canceled!");
}
Titus
fonte
Por que você está usando t.Start()e não Task.Run()?
Xander Luciano
1
@XanderLuciano: Neste exemplo não há um motivo específico e Task.Run () teria sido a melhor escolha.
Tito
5

Você deve passar o CancellationTokenpara a Tarefa, que monitorará periodicamente o token para ver se o cancelamento é solicitado.

CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
CancellationToken token = cancellationTokenSource.Token;  
Task task = Task.Run(() => {     
  while(!token.IsCancellationRequested) {
      Console.Write("*");         
      Thread.Sleep(1000);
  }
}, token);
Console.WriteLine("Press enter to stop the task"); 
Console.ReadLine(); 
cancellationTokenSource.Cancel(); 

Nesse caso, a operação será encerrada quando o cancelamento for solicitado e o Taskterá um RanToCompletionestado. Se você quiser ser informado de que sua tarefa foi cancelada , use ThrowIfCancellationRequestedpara lançar uma OperationCanceledExceptionexceção.

Task task = Task.Run(() =>             
{                 
    while (!token.IsCancellationRequested) {
         Console.Write("*");                      
        Thread.Sleep(1000);                 
    }           
    token.ThrowIfCancellationRequested();               
}, token)
.ContinueWith(t =>
 {
      t.Exception?.Handle(e => true);
      Console.WriteLine("You have canceled the task");
 },TaskContinuationOptions.OnlyOnCanceled);  

Console.WriteLine("Press enter to stop the task");                 
Console.ReadLine();                 
cancellationTokenSource.Cancel();                 
task.Wait(); 

Espero que isso ajude a entender melhor.

Mahbubur Rahman
fonte