Як користуватися властивістю CancellationToken?


117

Порівняно з попереднім кодом для класу 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методів асинхронізації, довгих методів опитування та використання зворотного виклику.
Ехтеш Чудхурі

У цій статті показані варіанти та необхідність обробки токена відповідно до вашого конкретного випадку.
Огнян Димитров

Відповіді:


140

Ви можете реалізувати свій метод роботи наступним чином:

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

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

ОНОВЛЕННЯ: Я вважаю за краще не писати, while (!cancelToken.IsCancellationRequested)тому що часто є кілька точок виходу, де ви можете перестати виконувати безпечне виконання через тіло циклу, і цикл, як правило, має певну логічну умову для виходу (перегляд всіх елементів колекції тощо). Тому я вважаю, що краще не змішувати ці умови, оскільки вони мають інший намір.

Попереджувальна примітка про уникнення CancellationToken.ThrowIfCancellationRequested():

Коментар в питанні по Імон Nerbonne :

... замінюючи ThrowIfCancellationRequestedкупу чеків на IsCancellationRequestedвиходи витончено, як йдеться у цій відповіді. Але це не лише деталізація реалізації; що впливає на поведінку, що спостерігається: завдання закінчуватиметься не в скасованому стані, а в RanToCompletion. І це може вплинути не тільки на явні перевірки стану, але і, більш тонко, на ланцюжок завдань, наприклад ContinueWith, залежно від TaskContinuationOptionsвикористовуваного. Я б сказав, що уникати ThrowIfCancellationRequestedнебезпечних порад.


1
Дякую! Це не випливає з тексту в Інтернеті, досить авторитетного (книга "C # 4.0 в горішці"?), Яку я цитував. Не могли б ви дати мені довідку про "завжди"?
Повний захист

1
Це випливає з практики та досвіду =). Я не можу згадати, звідки я це знаю. Я використовував "вам завжди потрібно", тому що ви фактично можете перервати робочу нитку за винятком зовні, використовуючи Thread.Abort (), але це дуже погана практика. До речі, використання CancellationToken.ThrowIfCancellationRequested () також "обробляти скасування самостійно", просто інший спосіб зробити це.
Сашко

1
@OleksandrPshenychnyy я мав на увазі заміну while (true) на while (! CancelToken.IsCancellationRequested). Це було корисно! Дякую!
Дуг Доусон

1
@Fulproof Не існує загального способу для скасування виконуваного коду, оскільки час виконання не є достатньо розумним, щоб знати, де процес може бути перерваний. В деяких випадках можливо просто вийти з циклу, в інших випадках потрібна більш складна логіка, тобто транзакції потрібно повернути назад, ресурси потрібно звільнити (наприклад, ручки файлів або мережеві з'єднання). Ось чому немає жодного магічного способу скасування завдання без необхідності написати якийсь код. Те, про що ви думаєте, - це як вбити процес, але це не скасувати, це одна з найгірших речей, яка може статися з програмою, тому що не може очистити.
користувач3285954

1
@kosist Ви можете використовувати CancellationToken.None, якщо ви не плануєте скасовувати операцію, яку ви починаєте вручну. Звичайно, коли вбивається системний процес, все переривається, і CancellationToken не має нічого спільного з цим. Так що так, вам слід створити CancellationTokenSource лише тоді, коли вам дійсно потрібно використовувати його для скасування операції. Немає сенсу створювати те, що не використовуєш.
Сашко

26

@ BrainSlugs83

Вам не слід сліпо довіряти всьому, розміщеному на stackoverflow. Коментар у коді Jens невірний, параметр не контролює, чи є винятки кинуті чи ні.

MSDN дуже ясно, що цей параметр контролює, ви його читали? http://msdn.microsoft.com/en-us/library/dd321703(v=vs.110).aspx

Якщо throwOnFirstExceptionце правда, виклик буде негайно розповсюджуватися з виклику Скасувати, запобігаючи обробці решти зворотних викликів та скасування операцій. Якщо throwOnFirstExceptionпомилково, це перевантаження буде агрегувати будь-які винятки, кинуті на AggregateException, таким чином, що один викид зворотного виклику не виключає виконання інших зареєстрованих зворотних зворотів.

Ім'я змінної також невірно, оскільки "Скасувати" викликається CancellationTokenSourceне самим маркером, а джерело змінює стан кожного маркера, яким він керує.


Також перегляньте тут документацію (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);

Рішення Anther є користувачем Timer у Xamarin.Forms, зупиніть таймер, коли програма goto background https://xamarinhelp.com/xamarin-forms-timer/


10

Ви можете використовувати ThrowIfCancellationRequestedбез обробки виняток!

Використання ThrowIfCancellationRequestedпризначене для використання зсередини Task(не a Thread). При використанні в межах A Taskвам не доведеться самостійно обробляти виняток (і отримувати помилку Unhandled Exception). Це призведе до залишення Task, і Task.IsCancelledвластивість буде True. Не потрібно виняток для обробки.

У вашому конкретному випадку змініть Threadна "a" Task.

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

if (t.IsCancelled)
{
    Console.WriteLine("Canceled!");
}

Чому ви використовуєте, t.Start()а ні Task.Run()?
Ксандер Лучано

1
@XanderLuciano: У цьому прикладі немає конкретної причини, і Task.Run () був би кращим вибором.
Тит

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

Сподіваюся, це допомагає зрозуміти краще.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.