如何使用 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 属性?的主要内容,如果未能解决你的问题,请参考以下文章
C# CancellationToken 如何与 SqlConnection.OpenAsync(token) 一起使用?
如何强制 IAsyncEnumerable 尊重 CancellationToken
C#:使用 CancellationToken 取消 MySqlCommand,给出 NULLReferenceException