C#中提供了CancellationTokenSource 来实现Task 的取消,方法就是在Task异步循环中检测任务是否被取消。最近正在学习C#的任务异步模型,因此撰文以记之。

无法被取消的任务

原本以为Task 是运行在被.NET管理的线程池上的,可以直接通过Task.Run 传入的CancellationToken 进行取消。结果剧本不是我想的那样,这样根本停不下来。因此还是需要在循环中去判断任务是否被取消。

static async Task TaskCannotCancellationAsync()
{
    CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
    CancellationToken token = cancellationTokenSource.Token;
    Task task = Task.Run(() =>
    {

        Console.WriteLine("Task Can not Cancellation Running...");
        Thread.Sleep(1000);
        Console.WriteLine("Task Can not Cancellation Running...");
        Console.WriteLine("Task Finished");

    }, cancellationTokenSource.Token);

    cancellationTokenSource.Token.Register(() => Console.WriteLine("Cancel Task."));
    cancellationTokenSource.CancelAfter(500);
    
    await task;
}

解下来介绍正确的可以被取消的异步任务写法。

基于异常的任务取消

我们在Task 的任务循环中检测Token 是否被取消。而取消之后就抛出异常,通过异常处理来中断原有的任务。

static async Task TaskCancellationAsync()
{
    CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
    CancellationToken token = cancellationTokenSource.Token;
    Task task = Task.Run(async () =>
    {

        token.ThrowIfCancellationRequested();

        bool moreToDo = true;
        while (moreToDo)
        {
            Console.WriteLine("Task Cancellation Running...");
            if (token.IsCancellationRequested)
            {
                token.ThrowIfCancellationRequested();
            }
            await Task.Delay(1000);
        }
        await Task.Delay(1000);
        Console.WriteLine("Task Finished");

    }, cancellationTokenSource.Token);

    cancellationTokenSource.Token.Register(() => Console.WriteLine("Cancel Task."));
    cancellationTokenSource.CancelAfter(500);

    try
    {
        await task;
    }
    catch (OperationCanceledException e)
    {
        Console.WriteLine($"{nameof(OperationCanceledException)} thrown with message: {e.Message}");
    }
    finally
    {
        cancellationTokenSource.Dispose();
    }
}

取消线程循环

既然我们可以这样取消一个死循环,那么博主就想试试看CancellationTokenSource 是否可以用于取消一个普通的线程。

static void ThreadProc(object? token)
{
    CancellationToken? cancellationToken = (CancellationToken?)token;
    while (!(cancellationToken?.IsCancellationRequested ?? true))
    {
        Console.WriteLine("ThreadMethod Working...");
        Thread.Sleep(100);
    }
}

static void ThreadAsync()
{
    CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
    Thread thread = new Thread(new ParameterizedThreadStart(ThreadProc));
    thread.Start(cancellationTokenSource.Token);
    cancellationTokenSource.CancelAfter(500);
}

测试后发现这么写也可以实现线程工作循环的取消。

完整的实验输出效果

gjF5PXQ3XRt8gEeg46LDMC4ONa9sX5mipc1o_jSj2do.gif

参考链接

https://cloud.tencent.com/developer/article/1895682

https://www.cnblogs.com/shanfeng1000/p/13402152.html

https://learn.microsoft.com/zh-cn/dotnet/standard/threading/cancellation-in-managed-threads