取消任务

>>返回《C# 并发编程》

CancellationToken.None 是一个等同于默认的特殊值,表示这个方法是永远不会被取消的。

实例代码

static async Task CancelableMethodAsync(CancellationToken token)
{
    await Task.Delay(1000, token);
    throw new ArgumentException();
}
public static async Task IssueCancelRequestAsync()
{
    var cts = new CancellationTokenSource();
    var task = CancelableMethodAsync(cts.Token);
    // 这里,操作在正常运行。
    // 发出取消请求。
    cts.Cancel();
    //(异步地)等待操作结束。
    try
    {
        await task;
        // 如运行到这里,说明在取消请求生效前,操作正常完成 。
    }
    catch (OperationCanceledException ex)
    {
        // 如运行到这里,说明操作在完成前被取消。
        System.Console.WriteLine(ex.GetType().Name);
    }
    catch (Exception ex)
    {
        // 如运行到这里,说明在取消请求生效前,操作出错并结束。
        System.Console.WriteLine(ex.GetType().Name);
    }
}

输出:

TaskCanceledException

1. 取消请求

public static int CancelableMethod(CancellationToken cancellationToken)
{
    for (int i = 0; i != 100000; ++i)
    {
        // cancellationToken.WaitHandle.WaitOne(1000);
        Thread.Sleep(1);
        // 这里做一些计算工作。
        if (i % 1000 == 0)
            cancellationToken.ThrowIfCancellationRequested();
    }
    return 42;
}

2. 超时后取消

public static async Task IssueTimeoutAsync()
{
    Stopwatch sw = Stopwatch.StartNew();
    try
    {
        var cts = new CancellationTokenSource();
        var token = cts.Token;
        cts.CancelAfter(TimeSpan.FromSeconds(2));
        await Task.Delay(TimeSpan.FromSeconds(4), token);
    }
    finally
    {
        System.Console.WriteLine($"{sw.ElapsedMilliseconds}ms");
    }
}

输出:

2004ms
Unhandled Exception: ... ...

只要执行代码时用到了超时,就该使用 CancellationTokenSourceCancelAfter (或者用构造函数)。虽然还有其他途径可实现这个功能,但是使用现有的取消体系是最简单也是最高效的。

3. 取消并行

public class Matrix
{
    public void Rotate(float degrees) { }
}

//只做展示
public static void RotateMatrices(IEnumerable<Matrix> matrices, float degrees, CancellationToken token)
{
    Parallel.ForEach(matrices, new ParallelOptions
    {
        CancellationToken = token
    },
    matrix => matrix.Rotate(degrees));
}

4. 取消响应式代码

注入取消请求

  • 某一个层次的代码需要响应取消请求,同时它本身也要向下一层代码发出取消请求(但不会向上传递)。
public static async Task RunGetWithTimeoutAsync()
{
    CancellationTokenSource source = new CancellationTokenSource();
    await GetWithTimeoutAsync("http://www.baidu.com", source.Token);
}

public static async Task<HttpResponseMessage> GetWithTimeoutAsync(string url, CancellationToken cancellationToken)
{
    var client = new HttpClient();
    using (var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
    {
        cts.CancelAfter(TimeSpan.FromMilliseconds(100));
        var combinedToken = cts.Token;
        return await client.GetAsync(url, combinedToken);
    }
}

输出:

Unhandled Exception: Unhandled exception: System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.AggregateException: One or more errors occurred. (A task was canceled.) ---> System.Threading.Tasks.TaskCanceledException ... ...

5. 与其他取消体系的互操作

有一些外部的或以前遗留下来的代码采用了非标准的取消模式。现在要用标准的CancellationToken 来控制这些代码

public static async Task RunPingAsync()
{
    var cts = new CancellationTokenSource();
    var task = PingAsync("192.168.0.101", cts.Token);
    //cts.Cancel();
    await task;
}
public static async Task<PingReply> PingAsync(string hostNameOrAddress, CancellationToken cancellationToken)
{
    Stopwatch sw = Stopwatch.StartNew();
    try
    {
        var ping = new Ping();
        using (cancellationToken.Register(() => ping.SendAsyncCancel()))
        {
            return await ping.SendPingAsync(hostNameOrAddress);
        }
    }
    finally
    {
        System.Console.WriteLine($"{sw.ElapsedMilliseconds}ms");
    }
}

注意: 为了避免内存和资源的泄漏,一旦不再需要使用回调函数了,就要释放这个回调函数注册。

posted @ 2020-02-01 16:03  大师兄石头  阅读(488)  评论(0编辑  收藏  举报