如何使用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/zh-cn/dotnet/standard/threading / ...上有一些很好的示例,这些示例CancellationTokenSource与异步方法一起使用,长时间运行的方法与轮询一起使用以及回调。
Ehtesh Choudhury

本文介绍了根据您的具体情况而拥有并需要处理令牌的选项。
Ognyan Dimitrov

Answers:


140

您可以按以下方式实现您的工作方法:

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

而已。您始终需要自己处理取消-在适当的时候退出方法退出(以便您的工作和数据处于一致的状态)

更新:我不喜欢写,while (!cancelToken.IsCancellationRequested)因为通常很少有退出点可以停止跨循环主体安全地执行,并且循环通常有一些逻辑条件可以退出(遍历集合中的所有项目等)。因此,我认为最好不要混合使用这些条件,因为它们的意图不同。

有关避免的警告CancellationToken.ThrowIfCancellationRequested()

Eamon Nerbonne有问题评论

…… 如答案所示,ThrowIfCancellationRequested用一堆IsCancellationRequested优雅的出口检查代替。但这不仅仅是实现细节;这会影响可观察到的行为:任务将不再以已取消状态结束,而是以结束RanToCompletion。这不仅会影响显式状态检查,而且还可能更细微地影响ContinueWith根据TaskContinuationOptions使用的任务链,例如。我会说避免ThrowIfCancellationRequested是危险的建议。


1
谢谢!我引用的是网上权威的文章(不是《 C#4.0 in a Nutshell》吗?),这并不是从在线文本中得出的。您能给我参考一下“总是”吗?
完美的

1
这来自实践和经验=)。我不记得从哪里知道这一点。我使用“您始终需要”,因为您实际上可以使用Thread.Abort()从外部中断工作线程,但这是一个非常糟糕的做法。顺便说一句,使用CancellationToken.ThrowIfCancellationRequested()也是“自己处理取消”,这是另一种方式。
萨沙

1
@OleksandrPshenychnyy我的意思是用while(!cancelToken.IsCancellationRequested)替换while(true)。这很有帮助!谢谢!
Doug Dawson 2014年

1
@Fulproof对于运行时,没有通用的方法来取消正在运行的代码,因为运行时不够智能,无法知道可以在何处中断进程。在某些情况下,可以简单地退出循环,在其他情况下,则需要更复杂的逻辑,即必须回滚事务,必须释放资源(例如文件句柄或网络连接)。这就是为什么没有一种无需编写代码即可取消任务的神奇方法的原因。您的想法就像杀死一个进程,但这并不是取消,这是应用程序可能发生的最糟糕的事情之一,因为无法清除。
user3285954 2014年

1
@kosist如果您不打算取消手动启动的操作,则可以使用CancellationToken.None。当然,当系统进程被杀死时,一切都会中断,而CancellationToken与之无关。因此,是的,仅在确实需要使用CancellationTokenSource来取消操作时才应该创建它。创建您不使用的东西是没有意义的。
萨沙

26

@ BrainSlugs83

您不应该盲目地信任stackoverflow上发布的所有内容。Jens代码中的注释不正确,该参数不控制是否引发异常。

MSDN非常清楚该参数控制什么,您读过吗? http://msdn.microsoft.com/zh-CN/library/dd321703(v=vs.110).aspx

如果throwOnFirstException为true,则异常将立即从对Cancel的调用中传播出去,从而阻止处理其余的回调和可取消的操作。如果 throwOnFirstException为false,则此重载会将抛出的所有异常汇总到中AggregateException,以使抛出异常的一个回调不会阻止其他已注册的回调被执行。

变量名也是错误的,因为取消调用CancellationTokenSource不是在令牌本身上调用的,并且源更改了它管理的每个令牌的状态。


还可以在此处查看有关取消令牌的建议用法的文档(TAP): docs.microsoft.com/zh-cn/dotnet/standard/…– Epstone
2017年

1
这是非常有用的信息,但根本无法回答所提出的问题。
11nallan11年

16

您可以使用取消令牌创建任务,当您将应用转到后台时,可以取消此令牌。

您可以在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(而不是Thread)内部使用。当在中使用时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!");
}

你为什么要使用t.Start()而不是Task.Run()
Xander Luciano

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

希望这有助于更好地理解。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.