在.Net和C#中运行异步代码相当简单,因为我们有时候需要取消正在进行的异步操作,通过本文,可以掌握 通过CancellationToken取消任务(包括non-cancellable任务)。

 早期

早期.Net 使用 BackgroundWorker 完成异步长时间运行操作。
可以使用CacnelAsync方法设置 CancellationPending = true
private void BackgroundLongRunningTask(object sender, DoWorkEventArgs e)
{
    BackgroundWorker worker = (BackgroundWorker)sender;

    ; i <= ; i++)
    {
        if (worker.CancellationPending == true)
        {
            e.Cancel = true;
            break;
        }

        // Do something
    }
}

已经不再推荐这种方式来完成异步和长时间运行的操作,但是大部分概念在现在依旧可以使用。

 Task横空出世

Task代表一个异步操作,该类表示一个异步不返回值的操作, 泛型版本Task<TResult>表示异步有返回值的操作
可使用async/await 语法糖代码去完成异步操作。
 
以下创建一个简单的长时间运行的操作:
/// <summary>
/// Compute a value for a long time.
/// </summary>
/// <returns>The value computed.</returns>
/// <param name="loop">Number of iterations to do.</param>
private static Task<decimal> LongRunningOperation(int loop)
{
    // Start a task and return it
    return Task.Run(() =>
    {
        ;

        // Loop for a defined number of iterations
        ; i < loop; i++)
        {
            // Do something that takes times like a Thread.Sleep in .NET Core 2.
            Thread.Sleep();
            result += i;
        }

        return result;
    });
}
// 这里我们使用Thread.Sleep 模仿长时间运行的操作

简单异步调用代码:

public static async Task ExecuteTaskAsync()
{
    Console.WriteLine(nameof(ExecuteTaskAsync));
    Console.WriteLine());
    Console.WriteLine("Press enter to continue");
    Console.ReadLine();
}

敲黑板: C#取消异步操作分为

① 让代码可取消(Cancellable)

因为一些原因,长时间运行的操作花费了 冗长的时间(需要取消,避免占用资源); 或者不愿意再等待执行结果了
我们会取消异步操作。
 
为完成目的需要在 长时间运行的异步任务中传入CancellationToken:
/// <summary>
/// Compute a value for a long time.
/// </summary>
/// <returns>The value computed.</returns>
/// <param name="loop">Number of iterations to do.</param>
/// <param name="cancellationToken">The cancellation token.</param>
private static Task<decimal> LongRunningCancellableOperation(int loop, CancellationToken cancellationToken)
{
    Task<decimal> task = null;

    // Start a task and return it
    task = Task.Run(() =>
    {
        ;

        // Loop for a defined number of iterations
        ; i < loop; i++)
        {
            // Check if a cancellation is requested, if yes,
            // throw a TaskCanceledException.

            if (cancellationToken.IsCancellationRequested)
                throw new TaskCanceledException(task);

            // Do something that takes times like a Thread.Sleep in .NET Core 2.
            Thread.Sleep();
            result += i;
        }

        return result;
    });

    return task;
}
在长时间运行的操作中监测 IsCancellationRequested方法 (当前是否发生取消命令),这里我倾向去包装一个TaskCanceledException异常类(给上层方法调用者更多处理的可能性); 当然可以调用ThrowIfCancellationRequested方法抛出OperationCanceledException异常。

② 触发取消命令

CancellationToken结构体相当于打入在异步操作内部的楔子,随时等候后方发来的取消命令
操纵以上CancellationToken状态的对象是 CancellationTokenSource,这个对象是取消操作的命令发布者。
 
默认的构造函数就支持了 超时取消:
//  以下代码 利用 CancellationSource默认构造函数 完成超时取消
public static async Task ExecuteTaskWithTimeoutAsync(TimeSpan timeSpan)
{
    Console.WriteLine(nameof(ExecuteTaskWithTimeoutAsync));

    using (var cancellationTokenSource = new CancellationTokenSource(timeSpan))
    {
        try
        {
            , cancellationTokenSource.Token);
            Console.WriteLine("Result {0}", result);
        }
        catch (TaskCanceledException)
        {
            Console.WriteLine("Task was cancelled");
        }
    }
    Console.WriteLine("Press enter to continue");
    Console.ReadLine();
}

------------------------------------------------------------------------------------------------------------

附①: 高阶操作,完成手动取消:

自然我们关注到 CancellationSource 的几个方法, 要想在异步操作的时候 手动取消操作,需要建立另外的线程 等待手动取消操作的指令。
public static async Task ExecuteManuallyCancellableTaskAsync()
{
    Console.WriteLine(nameof(ExecuteManuallyCancellableTaskAsync));

    using (var cancellationTokenSource = new CancellationTokenSource())
    {
        // Creating a task to listen to keyboard key press
        var keyBoardTask = Task.Run(() =>
        {
            Console.WriteLine("Press enter to cancel");
            Console.ReadKey();

            // Cancel the task
            cancellationTokenSource.Cancel();
        });

        try
        {
            , cancellationTokenSource.Token);

            var result = await longRunningTask;
            Console.WriteLine("Result {0}", result);
            Console.WriteLine("Press enter to continue");
        }
        catch (TaskCanceledException)
        {
            Console.WriteLine("Task was cancelled");
        }

        await keyBoardTask;
    }
}// 以上是一个控制台程序,异步接收控制台输入,发出取消命令。

附②:高阶操作,取消 non-Cancellable任务 :

有时候,异步操作代码并不提供 对 Cancellation的支持,也就是以上长时间运行的异步操作
LongRunningCancellableOperation(int loop, CancellationToken cancellationToken) 并不提供参数2的传入,相当于不允许 打入楔子。
 
这时我们怎样取消 这样的non-Cancellable 任务?
 
可考虑利用 Task.WhenAny( params tasks) 操作曲线取消:
  • 利用TaskCompletionSource 注册异步可取消任务
  • 等待待non-cancellable 操作和以上建立的 异步取消操作
private static async Task<decimal> LongRunningOperationWithCancellationTokenAsync(int loop, CancellationToken cancellationToken)
{
    // We create a TaskCompletionSource of decimal
    var taskCompletionSource = new TaskCompletionSource<decimal>();

    // Registering a lambda into the cancellationToken
    cancellationToken.Register(() =>
    {
        // We received a cancellation message, cancel the TaskCompletionSource.Task
        taskCompletionSource.TrySetCanceled();
    });

    var task = LongRunningOperation(loop);

    // Wait for the first task to finish among the two
    var completedTask = await Task.WhenAny(task, taskCompletionSource.Task);

    return await completedTask;
}

像上面代码一样执行取消命令 :

public static async Task CancelANonCancellableTaskAsync()
{
    Console.WriteLine(nameof(CancelANonCancellableTaskAsync));

    using (var cancellationTokenSource = new CancellationTokenSource())
    {
        // Listening to key press to cancel
        var keyBoardTask = Task.Run(() =>
        {
            Console.WriteLine("Press enter to cancel");
            Console.ReadKey();

            // Sending the cancellation message
            cancellationTokenSource.Cancel();
        });

        try
        {
            // Running the long running task
            , cancellationTokenSource.Token);
            var result = await longRunningTask;

            Console.WriteLine("Result {0}", result);
            Console.WriteLine("Press enter to continue");
        }
        catch (TaskCanceledException)
        {
            Console.WriteLine("Task was cancelled");
        }

        await keyBoardTask;
    }
}

  总结:

大多数情况下,我们不需要编写自定义可取消任务,因为我们只需要使用现有API。但要知道它是如何在幕后工作总是好的。
 
参考资料: 
 
作者:Julian_酱

感谢您的认真阅读,如有问题请大胆斧正,如果您觉得本文对你有用,不妨右下角点个或加关注。

本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置注明本文的作者及原文链接,否则保留追究法律责任的权利。

【.NET异步编程系列3】取消异步操作的更多相关文章

  1. 【异步编程】Part3:取消异步操作

    在.Net和C#中运行异步代码相当简单,因为我们有时候需要取消正在进行的异步操作,通过本文,可以掌握 通过CancellationToken取消任务(包括non-cancellable任务).  早期 ...

  2. 【.NET异步编程系列1】:await&async语法糖让异步编程如鱼得水

    前导 Asynchronous programming Model(APM)异步编程模型以BeginMethod(...) 和 EndMethod(...)结对出现. IAsyncResult Beg ...

  3. 异步编程系列第02章 你有什么理由使用Async异步编程

    p { display: block; margin: 3px 0 0 0; } --> 写在前面 在学异步,有位园友推荐了<async in C#5.0>,没找到中文版,恰巧也想提 ...

  4. 异步编程系列第04章 编写Async方法

    p { display: block; margin: 3px 0 0 0; } --> 写在前面 在学异步,有位园友推荐了<async in C#5.0>,没找到中文版,恰巧也想提 ...

  5. 异步编程系列第05章 Await究竟做了什么?

    p { display: block; margin: 3px 0 0 0; } --> 写在前面 在学异步,有位园友推荐了<async in C#5.0>,没找到中文版,恰巧也想提 ...

  6. 异步编程系列06章 以Task为基础的异步模式(TAP)

    p { display: block; margin: 3px 0 0 0; } --> 写在前面 在学异步,有位园友推荐了<async in C#5.0>,没找到中文版,恰巧也想提 ...

  7. 异步编程系列第01章 Async异步编程简介

    p { display: block; margin: 3px 0 0 0; } --> 2016.10.11补充 三个月过去了,回头来看,我不得不承认这是一系列失败的翻译.过段时间,我将重新翻 ...

  8. 【.NET异步编程系列2】掌控SynchronizationContext避免deadlock

    引言: 多线程编程/异步编程非常复杂,有很多概念和工具需要去学习,贴心的.NET提供Task线程包装类和await/async异步编程语法糖简化了异步编程方式. 相信很多开发者都看到如下异步编程实践原 ...

  9. C#与C++的发展历程第三 - C#5.0异步编程巅峰

    系列文章目录 1. C#与C++的发展历程第一 - 由C#3.0起 2. C#与C++的发展历程第二 - C#4.0再接再厉 3. C#与C++的发展历程第三 - C#5.0异步编程的巅峰 C#5.0 ...

随机推荐

  1. ruby簡單的代碼行統計工具

    看代码 # encoding: utf-8 class CodeLineStat attr_reader :code_lines def initialize @code_lines = 0 end ...

  2. JS windows对象的top属性

    原博文:http://www.jb51.net/article/44078.htm   本文为大家介绍下JS window对象的top.parent.opener含义,不了解的朋友可以参考下,希望对大 ...

  3. C# Dispose模式详细分析

    C#Dispose模式 目的: 为了及时释放宝贵的非托管资源和托管资源,并且保证资源在被gc回收的时候可以正确释放资源,同时兼顾执行效率 必须遵循的事实: 1 托管资源释放: 由另一线程的gc进行释放 ...

  4. 架构之Nginx(负载均衡/反向代理)

    Nginx ("engine x") 是一个高性能的 HTTP 和 反向代理 服务器 ,也是一个 IMAP/POP3/SMTP 代理 服务器 . Nginx 是由 Igor Sys ...

  5. Python之命名空间、闭包、装饰器

    一.命名空间 1. 命名空间 命名空间是一个字典,key是变量名(包括函数.模块.变量等),value是变量的值. 2. 命名空间的种类和查找顺序 - 局部命名空间:当前函数 - 全局命名空间:当前模 ...

  6. [ Java面试题 ]基础篇之二

    1.String s = new String("xyz");创建了几个StringObject?是否可以继承String类? 两个或一个都有可能,"xyz"对 ...

  7. nginx+php+mysql+wordpress搭建简单站点 安装及配置过程

    环境 阿里云ECS云服务器 CPU:1核 内存:2G 操作系统:Centos 7.3 x64 地域:华北 2(华北 2 可用区 A) 系统盘:40G 安装及配置 主要使用 nginx . php 和 ...

  8. Redis未授权访问

    最近在做校招题目的时候发现有问到未授权访问,特此搭建了诸多未授权访问的环境并且一一复现并做简单总结.再次记录下来 环境介绍 0x00环境搭建 我这里用到的是Microsoft(R) Windows(R ...

  9. JAVA中写时复制(Copy-On-Write)Map实现

    1,什么是写时复制(Copy-On-Write)容器? 写时复制是指:在并发访问的情景下,当需要修改JAVA中Containers的元素时,不直接修改该容器,而是先复制一份副本,在副本上进行修改.修改 ...

  10. @Controller和@RestController之间的区别

    1. Controller, RestController的共同点 都是用来表示Spring某个类的是否可以接收HTTP请求 2. Controller, RestController的不同点 @Co ...