CancellationToken 行为
CancellationToken Behavior
我想了解 CancellationToken
的工作原理,以及它如何取消任务。
为此,我创建了这个示例,它对 Task.Run()
和内部方法使用相同的标记 - HelpDoingSomething()
所以,我让任务运行500ms,然后我取消token,结果是:
第一条打印消息:“HelpDoingSomething cancelled”,然后是 “DoSomething cancelled”
class Program
{
static void Main(string[] args)
{
var cts = new CancellationTokenSource();
var myClass = new MyClass(cts.Token);
myClass.DoSomething();
Thread.Sleep(500);
cts.Cancel();
System.Console.ReadKey();
}
}
internal class MyClass
{
private CancellationToken token;
public MyClass(CancellationToken token)
{
this.token = token;
}
public void DoSomething()
{
Task.Run(() =>
{
HelpDoingSomething();
System.Console.WriteLine("DoSomething cancelled");
}, token);
}
private void HelpDoingSomething()
{
while (!token.IsCancellationRequested)
{
//Keep doing something
System.Console.Write(".");
}
System.Console.WriteLine("HelpDoingSomething cancelled");
}
}
我确切地知道我的方法 HelpDoingSomething()
检查是否在循环的每次迭代中请求了 cancellationToken。
我的问题是 Task.Run()
方法如何以及多久检查一次是否请求了取消令牌?
是否有可能 Task.Run()
会在 HelpDoingSomething()
之前进行检查,并且我只会看到打印的一条消息(“DoSomething cancelled”)?这意味着在此方法中可能无法正确处理逻辑。
传递给 Task.Run()
的取消标记在两个地方被检查:
(1) 任务真正开始前
如果在任务开始之前发出取消标记信号,任务将很快进入“WaitingToRun”状态,随后进入“Cancelled”状态。
在这种情况下将不会启动传递给任务的操作。
如果在调用 Task.Run()
后很快检查任务状态,则可能会观察到“WaitingToRun”状态,但这将是一个竞争条件,可能无法观察到该状态。但是,“已取消”状态最终总会被设置。
(2) 当传递给任务的动作抛出OperationCancelledException
.
与情况 (1) 一样,任务将短暂地转换到可以观察到的“WaitingToRun”状态,但与情况 (1) 不同的是,紧随其后的是“运行”状态(假设取消令牌在任务开始后被取消)。
如果与 OperationCancelledException
关联的令牌与传递给 Task.Run()
的令牌相同,任务将转换为“已取消”状态,否则将转换为“故障”状态.
详情请看这里:https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/task-cancellation
这是一个示例控制台应用程序,它演示了其中的一些情况:
using System;
using System.Threading;
using System.Threading.Tasks;
static class Program
{
public static void Main()
{
Console.WriteLine("Test with already-cancelled token, but not passed to Task.Run()");
CancellationTokenSource alreadyCancelled = new CancellationTokenSource();
alreadyCancelled.Cancel();
Task test1 = Task.Run(() => Test(alreadyCancelled.Token));
Console.WriteLine("test1 status: " + test1.Status); // Probably "WaitingToRun", but this has a race condition.
Thread.Sleep(200);
Console.WriteLine("test1 status: " + test1.Status); // Certainly "Faulted".
Console.WriteLine("\nTest with already-cancelled token passed to Task.Run()");
Task test2 = Task.Run(() => Test(alreadyCancelled.Token), alreadyCancelled.Token);
Console.WriteLine("test2 status: " + test2.Status); // Probably "WaitingToRun", but this has a race condition.
Thread.Sleep(200);
Console.WriteLine("test2 status: " + test2.Status); // Certainly "Cancelled".
Console.WriteLine("\nTest with token cancelled after starting task, but not passed to Task.Run()");
CancellationTokenSource cts3 = new CancellationTokenSource();
Task test3 = Task.Run(() => Test(cts3.Token));
Console.WriteLine("test3 status: " + test3.Status); // Probably "WaitingToRun", but this has a race condition.
Thread.Sleep(200);
Console.WriteLine("test3 status: " + test3.Status); // Certainly "Running".
cts3.Cancel();
Thread.Sleep(200);
Console.WriteLine("test3 status: " + test3.Status); // Certainly "Faulted".
Console.WriteLine("\nTest with token cancelled after starting task passed to Task.Run()");
CancellationTokenSource cts4 = new CancellationTokenSource();
Task test4 = Task.Run(() => Test(cts4.Token), cts4.Token);
Console.WriteLine("test4 status: " + test4.Status); // Probably "WaitingToRun", but this has a race condition.
Thread.Sleep(200);
Console.WriteLine("test4 status: " + test4.Status); // Certainly "Running".
cts4.Cancel();
Thread.Sleep(200);
Console.WriteLine("test4 status: " + test4.Status); // Certainly "Cancelled".
Console.ReadLine();
}
public static void Test(CancellationToken cancellation)
{
Console.WriteLine("Entering Test()");
cancellation.WaitHandle.WaitOne();
Console.WriteLine("Cancellation detected");
cancellation.ThrowIfCancellationRequested();
}
}
(评论里说“当然”有点假,但正常情况下200ms应该远远够观察到那个状态的转换了。如果不这样做,增加超时时间. 这不是在生产代码中要做的事情!)
这个输出是:
Test with already-cancelled token, but not passed to Task.Run()
test1 status: WaitingToRun
Entering Test()
Cancellation detected
test1 status: Faulted
Test with already-cancelled token passed to Task.Run()
test2 status: WaitingToRun
test2 status: Canceled
Test with token cancelled after starting task, but not passed to Task.Run()
test3 status: WaitingToRun
Entering Test()
test3 status: Running
Cancellation detected
test3 status: Faulted
Test with token cancelled after starting task passed to Task.Run()
test4 status: WaitingToRun
Entering Test()
test4 status: Running
Cancellation detected
test4 status: Canceled
请注意第二种情况“使用 already-cancelled 令牌进行测试传递给 Task.Run()”,Entering Test()
未写入控制台,因为未调用该操作。这是唯一一个根本没有调用操作的测试用例。
我想了解 CancellationToken
的工作原理,以及它如何取消任务。
为此,我创建了这个示例,它对 Task.Run()
和内部方法使用相同的标记 - HelpDoingSomething()
所以,我让任务运行500ms,然后我取消token,结果是:
第一条打印消息:“HelpDoingSomething cancelled”,然后是 “DoSomething cancelled”
class Program
{
static void Main(string[] args)
{
var cts = new CancellationTokenSource();
var myClass = new MyClass(cts.Token);
myClass.DoSomething();
Thread.Sleep(500);
cts.Cancel();
System.Console.ReadKey();
}
}
internal class MyClass
{
private CancellationToken token;
public MyClass(CancellationToken token)
{
this.token = token;
}
public void DoSomething()
{
Task.Run(() =>
{
HelpDoingSomething();
System.Console.WriteLine("DoSomething cancelled");
}, token);
}
private void HelpDoingSomething()
{
while (!token.IsCancellationRequested)
{
//Keep doing something
System.Console.Write(".");
}
System.Console.WriteLine("HelpDoingSomething cancelled");
}
}
我确切地知道我的方法 HelpDoingSomething()
检查是否在循环的每次迭代中请求了 cancellationToken。
我的问题是 Task.Run()
方法如何以及多久检查一次是否请求了取消令牌?
是否有可能 Task.Run()
会在 HelpDoingSomething()
之前进行检查,并且我只会看到打印的一条消息(“DoSomething cancelled”)?这意味着在此方法中可能无法正确处理逻辑。
传递给 Task.Run()
的取消标记在两个地方被检查:
(1) 任务真正开始前
如果在任务开始之前发出取消标记信号,任务将很快进入“WaitingToRun”状态,随后进入“Cancelled”状态。
在这种情况下将不会启动传递给任务的操作。
如果在调用 Task.Run()
后很快检查任务状态,则可能会观察到“WaitingToRun”状态,但这将是一个竞争条件,可能无法观察到该状态。但是,“已取消”状态最终总会被设置。
(2) 当传递给任务的动作抛出OperationCancelledException
.
与情况 (1) 一样,任务将短暂地转换到可以观察到的“WaitingToRun”状态,但与情况 (1) 不同的是,紧随其后的是“运行”状态(假设取消令牌在任务开始后被取消)。
如果与 OperationCancelledException
关联的令牌与传递给 Task.Run()
的令牌相同,任务将转换为“已取消”状态,否则将转换为“故障”状态.
详情请看这里:https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/task-cancellation
这是一个示例控制台应用程序,它演示了其中的一些情况:
using System;
using System.Threading;
using System.Threading.Tasks;
static class Program
{
public static void Main()
{
Console.WriteLine("Test with already-cancelled token, but not passed to Task.Run()");
CancellationTokenSource alreadyCancelled = new CancellationTokenSource();
alreadyCancelled.Cancel();
Task test1 = Task.Run(() => Test(alreadyCancelled.Token));
Console.WriteLine("test1 status: " + test1.Status); // Probably "WaitingToRun", but this has a race condition.
Thread.Sleep(200);
Console.WriteLine("test1 status: " + test1.Status); // Certainly "Faulted".
Console.WriteLine("\nTest with already-cancelled token passed to Task.Run()");
Task test2 = Task.Run(() => Test(alreadyCancelled.Token), alreadyCancelled.Token);
Console.WriteLine("test2 status: " + test2.Status); // Probably "WaitingToRun", but this has a race condition.
Thread.Sleep(200);
Console.WriteLine("test2 status: " + test2.Status); // Certainly "Cancelled".
Console.WriteLine("\nTest with token cancelled after starting task, but not passed to Task.Run()");
CancellationTokenSource cts3 = new CancellationTokenSource();
Task test3 = Task.Run(() => Test(cts3.Token));
Console.WriteLine("test3 status: " + test3.Status); // Probably "WaitingToRun", but this has a race condition.
Thread.Sleep(200);
Console.WriteLine("test3 status: " + test3.Status); // Certainly "Running".
cts3.Cancel();
Thread.Sleep(200);
Console.WriteLine("test3 status: " + test3.Status); // Certainly "Faulted".
Console.WriteLine("\nTest with token cancelled after starting task passed to Task.Run()");
CancellationTokenSource cts4 = new CancellationTokenSource();
Task test4 = Task.Run(() => Test(cts4.Token), cts4.Token);
Console.WriteLine("test4 status: " + test4.Status); // Probably "WaitingToRun", but this has a race condition.
Thread.Sleep(200);
Console.WriteLine("test4 status: " + test4.Status); // Certainly "Running".
cts4.Cancel();
Thread.Sleep(200);
Console.WriteLine("test4 status: " + test4.Status); // Certainly "Cancelled".
Console.ReadLine();
}
public static void Test(CancellationToken cancellation)
{
Console.WriteLine("Entering Test()");
cancellation.WaitHandle.WaitOne();
Console.WriteLine("Cancellation detected");
cancellation.ThrowIfCancellationRequested();
}
}
(评论里说“当然”有点假,但正常情况下200ms应该远远够观察到那个状态的转换了。如果不这样做,增加超时时间. 这不是在生产代码中要做的事情!)
这个输出是:
Test with already-cancelled token, but not passed to Task.Run()
test1 status: WaitingToRun
Entering Test()
Cancellation detected
test1 status: Faulted
Test with already-cancelled token passed to Task.Run()
test2 status: WaitingToRun
test2 status: Canceled
Test with token cancelled after starting task, but not passed to Task.Run()
test3 status: WaitingToRun
Entering Test()
test3 status: Running
Cancellation detected
test3 status: Faulted
Test with token cancelled after starting task passed to Task.Run()
test4 status: WaitingToRun
Entering Test()
test4 status: Running
Cancellation detected
test4 status: Canceled
请注意第二种情况“使用 already-cancelled 令牌进行测试传递给 Task.Run()”,Entering Test()
未写入控制台,因为未调用该操作。这是唯一一个根本没有调用操作的测试用例。