取消
目前,以下技术支持以标准方式取消长时间运行的操作:任务、并发集合类、并行LINQ、其他几种同步机制。
取消,是协作的,它不是强迫的
长时间运行的任务会检查它是否被取消,并相应地返回控制权
支持取消的方法,接收一个
CancellationToken参数。
- 长时间运行的操作,检查取消的方式:
IsCancellationRequested属性- 取消时,使用
CancellationToken的WaitHandle属性 - 使用
CancellationToken的Register方法。Register()接收以下参数:Action:在取消时调用- 或
ICancelableOperation,实现这个接口的对象的Cancel()方法在执行取消操作时调用
Parallel.For() 的取消
ParallelOptions的CancellationToken
CancellationToken- 通过
CancellationTokenSource来生成 - 可以调用
Register()方法来注册取消操作时要做的事情
- 通过
CancellationTokenSource- 实现了
ICancelableOperation接口 - 可以调用
Cancel()方法来取消操作
- 实现了
Parallel类- 验证
CancellationToken的结果,并取消操作 - 一旦取消操作,
For()就抛出一个OperationCanceledException
- 验证
public static void CancelParallelFor()
{
Console.WriteLine(nameof(CancelParallelFor));
var cts = new CancellationTokenSource();
cts.Token.Register(() => Console.WriteLine("*** token cancelled"));
// send a cancel after 500 ms
cts.CancelAfter(500);
try
{
ParallelLoopResult result =
Parallel.For(0, 100, new ParallelOptions
{
CancellationToken = cts.Token,
},
x =>
{
Console.WriteLine($"loop {x} started");
int sum = 0;
for (int i = 0; i < 100; i++)
{
Task.Delay(2).Wait();
sum += i;
}
Console.WriteLine($"loop {x} finished");
});
}
catch (OperationCanceledException ex)
{
Console.WriteLine(ex.Message);
}
Console.WriteLine();
}
输出:
CancelParallelFor
loop 0 started
loop 24 started
loop 12 started
loop 36 started
loop 48 started
loop 60 started
loop 72 started
loop 84 started
loop 96 started
*** token cancelled
loop 24 finished
loop 84 finished
loop 0 finished
loop 36 finished
loop 96 finished
loop 12 finished
loop 48 finished
loop 72 finished
loop 60 finished
The operation was canceled.
Task 的取消
- 先新建一个
CancellationTokenSource- 如果只需要一个
CancellationToken的话,可以使用默认的:Task.Factory.CancellationToken
- 如果只需要一个
- 调用
CancellationTokenSource的Cancel()或CancelAfter() - 将
CancellationToken作为第二个参数,传递给Task.Run()方法 - 检查
CancellationToken的IsCancellationRequested - 调用
CancellationToken的ThrowIfCancellationRequested
public static void CancelTask()
{
Console.WriteLine(nameof(CancelTask));
var cts = new CancellationTokenSource();
cts.Token.Register(() => Console.WriteLine("*** task cancelled"));
// send a cancel after 500 ms
cts.CancelAfter(500);
Task t1 = Task.Run(() =>
{
Console.WriteLine("in task");
for (int i = 0; i < 20; i++)
{
Task.Delay(100).Wait();
CancellationToken token = cts.Token;
if (token.IsCancellationRequested)
{
Console.WriteLine("cancelling was requested, " +
"cancelling from within the task");
token.ThrowIfCancellationRequested();
break;
}
Console.WriteLine("in loop");
}
Console.WriteLine("task finished without cancellation");
}, cts.Token);
try
{
t1.Wait();
}
catch (AggregateException ex)
{
Console.WriteLine($"exception: {ex.GetType().Name}, {ex.Message}");
foreach (var innerException in ex.InnerExceptions)
{
Console.WriteLine($"inner exception: {innerException.GetType()}," +
$"{innerException.Message}");
}
}
Console.WriteLine();
}
输出:
CancelTask
in task
in loop
in loop
in loop
in loop
*** task cancelled
cancelling was requested, cancelling from within the task
exception: AggregateException, One or more errors occurred. (A task was canceled.)
inner exception: System.Threading.Tasks.TaskCanceledException,A task was canceled.