协作式取消
对于长时间运行的计算限制操作来说,支持取消是一件很有用的特性。
为了取消一个操作,首先必须创建一个System.Threading.CancellationTokenSource对象
public sealed class CancellationTokenSource : IDisposable {
// A reference type
public CancellationTokenSource();
public Boolean IsCancellationRequested { get; }
public CancellationToken Token { get; } public void Cancel();
// Internally, calls Cancel passing false
public void Cancel(Boolean throwOnFirstException);
... }
public struct CancellationToken {
// A value type
public static CancellationToken None { get; }
// Very convenient
public Boolean IsCancellationRequested { get; }
// Called by non-Task invoked operations
public void ThrowIfCancellationRequested();
// Called by Task-invoked operations
// WaitHandle is signaled when the CancellationTokenSource is canceled
public WaitHandle WaitHandle { get; }
// GetHashCode, Equals, operator== and operator!= members are not shown
public Boolean CanBeCanceled { get; }
// Rarely used
public CancellationTokenRegistration Register(Action callback, Object state, Boolean useSynchronizationContext);
// Simpler overloads not shown }
在一个计算限制的循环操作中,可以定时调用CancellationToken 的IsCancellationRequested 属性,来了解循环是否应该提前终止,进而终止计算限制的操作。
完整示例
internal static class CancellationDemo {
public static void Main() {
CancellationTokenSource cts = new CancellationTokenSource();
// Pass the CancellationToken and the number-to-count-to into the operation
ThreadPool.QueueUserWorkItem(o => Count(cts.Token, 1000));
Console.WriteLine("Press <Enter> to cancel the operation.");
Console.ReadLine();
cts.Cancel(); // If Count returned already, Cancel has no effect on it
// Cancel returns immediately, and the method continues running here...
Console.ReadLine();
}
private static void Count(CancellationToken token, Int32 countTo) {
for (Int32 count = 0; count <countTo; count++) {
if (token.IsCancellationRequested) {
Console.WriteLine("Count is cancelled");
break; // Exit the loop to stop the operation
}
Console.WriteLine(count);
Thread.Sleep(200); // For demo, waste some time
}
Console.WriteLine("Count is done");
}
}
如果愿意,可以登记一个或多个方法在取消一个CancellationTokenSource时调用。然而每个回调方法都是通过CancellationToken 的Register方法来登记的。要向这个方法传递一个Actio<Object>的委托和一个布尔值,该值指定是否要使用线程的SynchronizationContext来调用委托。如果为false,那么调用Cancel的线程会顺序执行所有登记方法。如果为true那么回调会被Send给已捕获的SynchronizationContext对象,后者决定由哪个线程调用回调。
CancellationTokenSource 的Cancel方法,如果传递True,那么抛出的第一个异常的第一个回调方法会阻止其他回调方法的执行,异常也会从Cancel中抛出。如果为False,那么所有已登记的回调方法都会调用,所有未处理异常都会被添加到一个集合中,Cancel会抛出一个AggregateException。
可以通过链接另一组CancellationTokenSource来新建一个CancellationTokenSource对象,任何一个链接的CancellationTokenSource被取消,那么新的CancellationTokenSource就会被取消
// Create a CancellationTokenSource
var cts1 = new CancellationTokenSource(); cts1.Token.Register(() => Console.WriteLine("cts1 canceled"));
// Create another CancellationTokenSource
var cts2 = new CancellationTokenSource();
cts2.Token.Register(() => Console.WriteLine("cts2 canceled"));
// Create a new CancellationTokenSource that is canceled when cts1 or ct2 is canceled
var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cts1.Token, cts2.Token);
linkedCts.Token.Register(() => Console.WriteLine("linkedCts canceled"));
// Cancel one of the CancellationTokenSource objects (I chose cts2) cts2.Cancel();
// Display which CancellationTokenSource objects are canceled
Console.WriteLine("cts1 canceled={0}, cts2 canceled={1},
linkedCts canceled={2}", cts1.IsCancellationRequested,
cts2.IsCancellationRequested,
linkedCts.IsCancellationRequested);
运行结果
linkedCts canceled
cts2 canceled
cts1 canceled=False, cts2 canceled=True, linkedCts canceled=True