如何使用 CancellationToken 属性?

Posted

技术标签:

【中文标题】如何使用 CancellationToken 属性?【英文标题】:How to use the CancellationToken property? 【发布时间】:2013-02-10 15:35:19 【问题描述】:

与前面的代码for class RulyCanceler相比,我想用CancellationTokenSource运行代码。

如Cancellation Tokens 中所述,我该如何使用它,即不抛出/捕获异常?我可以使用IsCancellationRequested 属性吗?

我尝试这样使用它:

cancelToken.ThrowIfCancellationRequested();

try

  new Thread(() => Work(cancelSource.Token)).Start();

catch (OperationCanceledException)

  Console.WriteLine("Canceled!");

但这在方法Work(CancellationToken cancelToken) 中对cancelToken.ThrowIfCancellationRequested(); 产生了运行时错误:

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

【问题讨论】:

docs.microsoft.com/en-us/dotnet/standard/threading/… 有一些很好的例子,将CancellationTokenSource 与异步方法、长时间运行的方法与轮询以及使用回调一起使用。 This 文章根据您的具体情况显示您拥有和需要处理令牌的选项。 【参考方案1】:

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

private static void Work(CancellationToken cancelToken)

    while (true)
    
        if(cancelToken.IsCancellationRequested)
        
            return;
        
        Console.Write("345");
    

就是这样。您总是需要自己处理取消 - 在适当的时候退出方法(以便您的工作和数据处于一致状态)

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

关于避免CancellationToken.ThrowIfCancellationRequested()的注意事项:

Comment in questionEamon Nerbonne:

... 用一堆检查 IsCancellationRequested 替换 ThrowIfCancellationRequested 会优雅地退出,正如这个答案所说。但这不仅仅是一个实现细节;这会影响可观察行为:任务将不再以取消状态结束,而是以RanToCompletion 结束。这不仅会影响显式状态检查,而且更微妙地会影响任务链接,例如ContinueWith,取决于使用的 TaskContinuationOptions。我会说避免ThrowIfCancellationRequested 是危险的建议。

【讨论】:

谢谢!这不是来自网上的文字,相当权威(书“C# 4.0 in a Nutshell”?)我引用了。你能给我一个关于“总是”的参考吗? 这来自实践和经验=)。我不记得我从哪里知道的。我使用“你总是需要”,因为你实际上可以使用 Thread.Abort() 从外部中断工作线程,但这是一个非常糟糕的做法。顺便说一句,使用 CancellationToken.ThrowIfCancellationRequested() 也是“自己处理取消”,只是另一种方式。 while(!cancelToken.IsCancellationRequested) 可以吗? @OleksandrPshenychnyy 我的意思是用 while(!cancelToken.IsCancellationRequested) 替换 while(true)。这很有帮助!谢谢! @kosist 如果您不打算取消手动启动的操作,您可以使用 CancellationToken.None。当然,当系统进程被杀死时,一切都会被中断,而 CancellationToken 与此无关。所以是的,如果你真的需要使用它来取消操作,你应该只创建 CancellationTokenSource 。创造你不使用的东西是没有意义的。【参考方案2】:

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

您可以在 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/

【讨论】:

两个问题。首先,使用Task.Run()。其次,这不会像您认为的那样取消底层等待的任务。在这种情况下,您需要将cancelToken 传递给延迟:Task.Delay(10000, cancelToken)。通过令牌取消是合作的。它需要传递给您希望能够在链中取消的每个可等待对象。 关于上面的第 2 点,see section "CancellationToken" here.【参考方案3】:

您必须将CancellationToken 传递给Task,Task 将定期监控令牌以查看是否请求取消。

// CancellationTokenSource provides the token and have authority to cancel the token
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
CancellationToken token = cancellationTokenSource.Token;  

// Task need to be cancelled with CancellationToken 
Task task = Task.Run(async () =>      
  while(!token.IsCancellationRequested) 
      Console.Write("*");         
      await Task.Delay(1000);
  
, token);

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

在这种情况下,操作将在请求取消时结束,Task 将具有RanToCompletion 状态。如果您想确认您的任务已被取消,您必须使用ThrowIfCancellationRequested 来抛出OperationCanceledException 异常。

Task task = Task.Run(async () =>             
                 
    while (!token.IsCancellationRequested) 
         Console.Write("*");                      
         await Task.Delay(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(); 

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

【讨论】:

为什么cancelationToken没有传给Task.Delay()【参考方案4】:

可以在不处理异常的情况下使用ThrowIfCancellationRequested

ThrowIfCancellationRequested 的使用是为了在 Task(不是Thread)中使用。 在Task 中使用时,您不必自己处理异常(并获得未处理的异常错误)。这将导致离开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() @XanderLuciano:在这个例子中没有具体的原因,Task.Run() 会是更好的选择。 你没有显示 ThrowIfCancellationRequested

以上是关于如何使用 CancellationToken 属性?的主要内容,如果未能解决你的问题,请参考以下文章

如何取消 CancellationToken

C# CancellationToken 如何与 SqlConnection.OpenAsync(token) 一起使用?

如何强制 IAsyncEnumerable 尊重 CancellationToken

如何正确重置 CancellationToken?

C#:使用 CancellationToken 取消 MySqlCommand,给出 NULLReferenceException

CancellationToken 不能使用零超时