Skip to main content

取消任务

分类:  .Net技术 标签:  #异步编程 #基础 #.Net 发布于: 2023-06-04 19:09:31

我们前面几章学习了异步编程的基本用法和基本理论,本章来学习如何取消一个Task

用户可以取消还没有完成的任务,实际的基本原理也很简单,只需要给没个异步方法带入CancellationTokenSource的实例就可以了,下面的例子演示如何取消一个任务,您可以从这里找到示例代码:https://github.com/hylinux/azure-demo/tree/main/dotnet/basic/asyncdemo/CancelDemo

该实例的场景是取消从网络上下载内容的web客户端。

创建控制台应用

使用如下的命令行创建控制台应用

mkdir CancelDemo
cd CancelDemo
dotnet new console 

替换using语句

使用编辑器,例如VS code,打开Program.cs文件,将using statement替换为如下的内容

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

添加字段

在类Program中添加如下字段的定义:

static readonly CancellationTokenSource s_cts = new CancellationTokenSource();

static readonly HttpClient s_client = new HttpClient
{
    MaxResponseContentBufferSize = 1_000_000
};

static readonly IEnumerable<string> s_urlList = new string[]
{
    "https://docs.microsoft.com",
    "https://docs.microsoft.com/aspnet/core",
    "https://docs.microsoft.com/azure",
    "https://docs.microsoft.com/azure/devops",
    "https://docs.microsoft.com/dotnet",
    "https://docs.microsoft.com/dynamics365",
    "https://docs.microsoft.com/education",
    "https://docs.microsoft.com/enterprise-mobility-security",
    "https://docs.microsoft.com/gaming",
    "https://docs.microsoft.com/graph",
    "https://docs.microsoft.com/microsoft-365",
    "https://docs.microsoft.com/office",
    "https://docs.microsoft.com/powershell",
    "https://docs.microsoft.com/sql",
    "https://docs.microsoft.com/surface",
    "https://docs.microsoft.com/system-center",
    "https://docs.microsoft.com/visualstudio",
    "https://docs.microsoft.com/windows",
    "https://docs.microsoft.com/xamarin"
};

这里CancellationTokenSource用作一个信号源,它会发送一个CancellationToken给到需要取消的方法。

更新Main方法

更新一下Main方法如下:

static async Task Main()
{
    Console.WriteLine("Application started.");
    Console.WriteLine("Press the ENTER key to cancel...\n");

    Task cancelTask = Task.Run(() =>
    {
        while (Console.ReadKey().Key != ConsoleKey.Enter)
        {
            Console.WriteLine("Press the ENTER key to cancel...");
        }

        Console.WriteLine("\nENTER key pressed: cancelling downloads.\n");
        s_cts.Cancel();
    });

    Task sumPageSizesTask = SumPageSizesAsync();

    await Task.WhenAny(new[] { cancelTask, sumPageSizesTask });

    Console.WriteLine("Application ending.");
}

注意到我们在主线程里创建了一个CPU-bound的异步方法(直接使用Task.run()来运行的异步方法), 然后再调用另外一个异步方法SumPageSizesAsync(), 最后使用Task.WhenAny()来判断列表中的任务完成。

创建异步方法SumPageSizesAsync()

这里我们创建我们需要的异步方法

static async Task SumPageSizesAsync()
{
    var stopwatch = Stopwatch.StartNew();

    int total = 0;
    foreach (string url in s_urlList)
    {
        int contentLength = await ProcessUrlAsync(url, s_client, s_cts.Token);
        total += contentLength;
    }

    stopwatch.Stop();

    Console.WriteLine($"\nTotal bytes returned:  {total:#,#}");
    Console.WriteLine($"Elapsed time:          {stopwatch.Elapsed}\n");
}

需要注意的是异步方法ProcessUrlAsync()的参数里有一个s_cts的变量,该变量即为CancellationTokenSource的实例。
这里看到的是如何定义一个可取消的调用,我们再来看如何定义这样的异步方法

定义可取消的异步方法

我们这里来定义可取消的异步方法:

static async Task<int> ProcessUrlAsync(string url, HttpClient client, CancellationToken token)
{
    HttpResponseMessage response = await client.GetAsync(url, token);
    byte[] content = await response.Content.ReadAsByteArrayAsync(token);
    Console.WriteLine($"{url,-60} {content.Length,10:#,#}");

    return content.Length;
}

好了,到这里整个实例算是定义完成了,可以看一下取消的效果。