Как использовать свойство CancellationToken?

118

По сравнению с предыдущим кодом для класса RulyCanceler я хотел запустить код с использованием CancellationTokenSource.

Как использовать его, как указано в разделе «Токены отмены» , то есть без генерации / перехвата исключения? Могу ли я использовать IsCancellationRequestedнедвижимость?

Я пытался использовать это так:

cancelToken.ThrowIfCancellationRequested();

и

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

но это дало ошибку времени выполнения cancelToken.ThrowIfCancellationRequested();в методе 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:

Код, который я успешно выполнил, перехватил OperationCanceledException в новом потоке:

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();
      }
    }
  }
}
Полная защита
источник
2
docs.microsoft.com/en-us/dotnet/standard/threading/… содержит несколько довольно хороших примеров использования CancellationTokenSourceасинхронных методов, длительных методов с опросом и использования обратного вызова.
Ehtesh Choudhury
В этой статье показаны варианты, которые у вас есть и которые необходимо обрабатывать токеном в соответствии с вашим конкретным случаем.
Огнян Димитров

Ответы:

140

Вы можете реализовать свой метод работы следующим образом:

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

Вот и все. Вам всегда нужно обрабатывать отмену самостоятельно - выходите из метода, когда подходит время для выхода (чтобы ваша работа и данные были в согласованном состоянии)

ОБНОВЛЕНИЕ: я предпочитаю не писать, while (!cancelToken.IsCancellationRequested)потому что часто есть несколько точек выхода, где вы можете безопасно прекратить выполнение по телу цикла, и цикл обычно имеет какое-то логическое условие для выхода (перебирать все элементы в коллекции и т. Д.). Поэтому я считаю, что лучше не смешивать эти условия, поскольку у них разные намерения.

Предупреждение о том, как избегать CancellationToken.ThrowIfCancellationRequested():

Комментарий, о котором идет речь, от Иамона Нербона :

... изящно заменив ThrowIfCancellationRequestedкучу проверок для IsCancellationRequestedвыходов, как говорится в этом ответе. Но это не просто деталь реализации; это влияет на наблюдаемое поведение: задача больше не будет завершаться в состоянии отмены, а будет в RanToCompletion. И это может повлиять не только на явные проверки состояния, но и, более тонко, на цепочку задач, например ContinueWith, в зависимости от TaskContinuationOptionsиспользуемого. Я бы сказал, что избегать ThrowIfCancellationRequested- опасный совет.

Саша
источник
1
Спасибо! Этого не следует из онлайн-текста, довольно авторитетного (книга «C # 4.0 in a Nutshell»?), Который я цитировал. Не могли бы вы дать мне ссылку на "всегда"?
Fulproof
1
Это исходит из практики и опыта =). Не могу вспомнить, откуда я это знаю. Я использовал «вам всегда нужно», потому что вы действительно можете прервать рабочий поток с исключением извне, используя Thread.Abort (), но это очень плохая практика. Между прочим, использование CancellationToken.ThrowIfCancellationRequested () также является «самостоятельной обработкой отмены», это просто другой способ сделать это.
Саша
1
@OleksandrPshenychnyy Я имел в виду заменить while (true) на while (! CancelToken.IsCancellationRequested). Это было полезно! Спасибо!
Дуг Доусон
1
@Fulproof Не существует универсального способа для среды выполнения отменить выполняющийся код, потому что среда выполнения недостаточно умна, чтобы знать, где процесс может быть прерван. В некоторых случаях можно просто выйти из цикла, в других случаях требуется более сложная логика, т. Е. Транзакции должны быть отменены, ресурсы должны быть освобождены (например, дескрипторы файлов или сетевые соединения). Вот почему нет волшебного способа отменить задачу, не написав код. То, что вы думаете, похоже на завершение процесса, но это не отмена, это одна из худших вещей, которые могут случиться с приложением, потому что не могут выполнить очистку.
user3285954
1
@kosist Вы можете использовать CancellationToken.None, если не планируете отменять операцию, которую запускаете вручную. Конечно, когда системный процесс убит, все прерывается, и CancellationToken не имеет к этому никакого отношения. Итак, да, вам следует создавать CancellationTokenSource только в том случае, если вам действительно нужно использовать его для отмены операции. Нет смысла создавать то, чем ты не пользуешься.
Саша
26

@ BrainSlugs83

Вы не должны слепо доверять всему, что написано в stackoverflow. Комментарий в коде Йенса неверен, параметр не контролирует, генерируются исключения или нет.

MSDN очень ясно, что этот параметр контролирует, вы его читали? http://msdn.microsoft.com/en-us/library/dd321703(v=vs.110).aspx

Если throwOnFirstExceptionистинно, исключение будет немедленно распространяться за пределы вызова Cancel, предотвращая обработку оставшихся обратных вызовов и отменяемых операций. Если задано значение throwOnFirstExceptionfalse, эта перегрузка будет агрегировать любые исключения, выданные в объект AggregateException, так что один обратный вызов, генерирующий исключение, не будет препятствовать выполнению других зарегистрированных обратных вызовов.

Имя переменной также неверно, потому что Cancel вызывается CancellationTokenSourceне для самого токена, а источник изменяет состояние каждого токена, которым он управляет.

user3285954
источник
Также ознакомьтесь с документацией (TAP) о предлагаемом использовании токена отмены: docs.microsoft.com/en-us/dotnet/standard/…
Epstone
1
Это очень полезная информация, но она совсем не отвечает на заданный вопрос.
11nallan11
16

Вы можете создать задачу с токеном отмены, когда вы приложение goto background, вы можете отменить этот токен.

Вы можете сделать это в 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);

Другое решение - это пользовательский таймер в Xamarin.Forms, остановить таймер, когда приложение переходит в фоновый режим https://xamarinhelp.com/xamarin-forms-timer/

Джесси Цзян
источник
10

Вы можете использовать ThrowIfCancellationRequestedбез обработки исключения!

Использование ThrowIfCancellationRequestedпредназначено для использования изнутри Task(а не a Thread). При использовании внутри a Taskвам не нужно самостоятельно обрабатывать исключение (и получать ошибку Unhandled Exception). Это приведет к выходу из Task, и Task.IsCancelledсвойство будет True. Обработка исключений не требуется.

В вашем конкретном случае, изменить Threadк Task.

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

if (t.IsCancelled)
{
    Console.WriteLine("Canceled!");
}
Titus
источник
Почему ты используешь, t.Start()а нет Task.Run()?
Ксандер Лучиано
1
@XanderLuciano: В этом примере нет конкретной причины, и Task.Run () был бы лучшим выбором.
Titus
5

Вы должны передать CancellationTokenзадачу Задаче, которая будет периодически отслеживать токен, чтобы узнать, запрашивается ли отмена.

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

В этом случае операция завершится, когда будет запрошена отмена, и у Taskобъекта будет RanToCompletionсостояние. Если вы хотите, чтобы вас признали, что ваша задача была отменена , вы должны использовать ThrowIfCancellationRequestedдля создания OperationCanceledExceptionисключения.

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

Надеюсь, это поможет лучше понять.

Махбубур Рахман
источник