背景

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

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(() =>
{
decimal result = ; // Loop for a defined number of iterations
for (int i = ; 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("Result {0}", await LongRunningOperation());
Console.WriteLine("Press enter to continue");
Console.ReadLine();
}

因为一些原因我们会取消异步操作:

  • 操作耗时较长,堵塞了其他正常请求;
  • 不愿意再等待执行结果了,手动取消

编写可取消的异步操作代码

其中关注

类CancellationTokenSource:给CancellationToken发出取消通知

结构体CancellationToken: 取消操作的通知。

CancellationToken结构体相当于打入在异步操作内部的楔子,随时等候CancellationTokenSource  发出的取消通知

 定义异步方法时候设定 CancelletionToken参数

那么这个异步方法即是Cancellable 的异步方法

/// <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(() =>
{
decimal result = ; // Loop for a defined number of iterations
for (int i = ; 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状态的对象是 CancellationTokenSource,这个对象是取消操作的命令发布者。

//  定义超时取消
public static async Task ExecuteTaskWithTimeoutAsync(TimeSpan timeSpan)
{
Console.WriteLine(nameof(ExecuteTaskWithTimeoutAsync)); using (var cancellationTokenSource = new CancellationTokenSource(timeSpan))
{
try
{
var result = await LongRunningCancellableOperation(, cancellationTokenSource.Token);
Console.WriteLine("Result {0}", result);
}
catch (TaskCanceledException)
{
Console.WriteLine("Task was cancelled");
}
}
Console.WriteLine("Press enter to continue");
Console.ReadLine();
}

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

手动取消操作

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
{
var longRunningTask = LongRunningCancellableOperation(, 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任务 :

有时候,部分第三方异步操作代码并不是可取消的,也就是以上长时间运行的异步操作LongRunningCancellableOperation(int loop, CancellationToken cancellationToken) 并不支持CancellationToken ,相当于不允许打入楔子。

这时我们怎样取消 这样的non-Cancellable 任务?

可考虑利用 Task.WhenAny( params tasks) 操作曲线取消:

  • 利用TaskCompletionSource 注册异步可取消任务
  • 等待待non-cancellable 操作和以上建立的 异步取消操作
private static async Task<decimal> LongRunningOperationWithCancellationTokenAsync(int loop, CancellationToken cancellationToken)
{
// 定义一个任务完成的消息源,任务取消的动作 绑定到该任务完成的动作上
var taskCompletionSource = new TaskCompletionSource<decimal>();
cancellationToken.Register(() =>
{
taskCompletionSource.TrySetCanceled();
}); var task = LongRunningOperation(loop);
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
var longRunningTask = LongRunningOperationWithCancellationTokenAsync(, 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。但要知道它是如何在幕后工作总是好的。

https://johnthiriet.com/cancel-asynchronous-operation-in-csharp/

https://stackoverflow.com/questions/4238345/asynchronously-wait-for-taskt-to-complete-with-timeout

https://github.com/App-vNext/Polly/wiki/Timeout

【异步编程】Part3:取消异步操作的更多相关文章

  1. C#:异步编程和线程的使用(.NET 4.5 )

    摘自:http://www.codeproject.com/Articles/996857/Asynchronous-programming-and-Threading-in-Csharp-N(葡萄城 ...

  2. 异步编程和线程的使用(.NET 4.5 )

    C#:异步编程和线程的使用(.NET 4.5 )   异步编程和线程处理是并发或并行编程非常重要的功能特征.为了实现异步编程,可使用线程也可以不用.将异步与线程同时讲,将有助于我们更好的理解它们的特征 ...

  3. C#:异步编程和线程的使用(.NET 4.5 ),异步方法改为同步执行

    摘自:http://www.codeproject.com/Articles/996857/Asynchronous-programming-and-Threading-in-Csharp-N(葡萄城 ...

  4. async和await关键字实现异步编程

    async和await关键字实现异步编程 异步编程   概念 异步编程核心为异步操作,该操作一旦启动将在一段时间内完成.所谓异步,关键是实现了两点:(1)正在执行的此操作,不会阻塞原来的线程(2)一旦 ...

  5. 【.NET异步编程系列3】取消异步操作

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

  6. [C#] 走进异步编程的世界 - 在 GUI 中执行异步操作

    走进异步编程的世界 - 在 GUI 中执行异步操作 [博主]反骨仔 [原文地址]http://www.cnblogs.com/liqingwen/p/5877042.html 序 这是继<开始接 ...

  7. 走进异步编程的世界 - 在 GUI 中执行异步操作

    转载:https://www.cnblogs.com/liqingwen/p/5877042.html 走进异步编程的世界 - 在 GUI 中执行异步操作 [博主]反骨仔 [原文地址]http://w ...

  8. c#异步编程(三)—ASP.NET MVC 异步控制器及EF异步操作

    ASP.NET MVC 异步控制器及EF异步操作 异步控制器 ASP.NET MVC2后开始了对异步请求管道的支持,异步请求管道的作用是允许web服务器处理长时间运行的请求,比如 那些花费大量时间等待 ...

  9. 把 HttpHandler.ashx 修改为 异步编程 异步操作

    在 ASP.NET 中,所有的处理程序类必须实现 IHttpHandler 接口或者实现 IHttpAsyncHandler 接口,这两个接口的区别是前者是一个同步接口,后者是一个异步处理模式的接口. ...

随机推荐

  1. 【题解】NOI2015软件包管理器

    [题解][P2146 NOI2015]软件包管理器 实际上就是树链剖分板子题. 对于\(install\)操作,直接查询它到\(0\)节点有多少已经安装了的,再用总数减去它. 对于\(uninstal ...

  2. Java for LeetCode 107 Binary Tree Level Order Traversal II

    Given a binary tree, return the bottom-up level order traversal of its nodes' values. (ie, from left ...

  3. spring-boot-starter-parent

    在官方文档的第三部分的13块讲述了引用的管理,官方推荐的是使用Maven和Gradle. 我一直在用的是maven,而且使用maven有些优势–spring-boot-starter-parent,这 ...

  4. Linux-3.14.12内存管理笔记【kmalloc与kfree实现】【转】

    本文转载自:http://blog.chinaunix.net/uid-26859697-id-5573776.html kmalloc()是基于slab/slob/slub分配分配算法上实现的,不少 ...

  5. HDU2243 考研路茫茫——单词情结 ——AC自动机、矩阵优化

    题目链接:https://vjudge.net/problem/HDU-2243 考研路茫茫——单词情结 Time Limit: 2000/1000 MS (Java/Others)    Memor ...

  6. XXL-Job集群

    底层已经实现好了 调度中心集群 调度中心支持集群部署,提升调度系统容灾和可用性. 调度中心集群部署时,几点要求和建议: DB配置保持一致: 登陆账号配置保持一致: 群机器时钟保持一致(单机集群忽视): ...

  7. Contiki进程间的交互

    之前都是从各个模块开始看起,从底层开始看起.应该改变一下思路,从高往下看,站得高看得远. 一.Main函数 源码:contiki-release-2-7\platform\stm32test\cont ...

  8. 一篇文章教你如何用R进行数据挖掘

    一篇文章教你如何用R进行数据挖掘 引言 R是一种广泛用于数据分析和统计计算的强大语言,于上世纪90年代开始发展起来.得益于全世界众多 爱好者的无尽努力,大家继而开发出了一种基于R但优于R基本文本编辑器 ...

  9. python的writelines读空行

    在文件中,如果遇到一个空白行,readline()并不会返回一个空串,因为每一行的末尾还有一个或多个分隔符,因此“空白行”至少会有一个换行符或者系统使用的其他符号.只有当真的读到文件末尾时,才会读到空 ...

  10. springAOP原理以及概念

    需求:1.拦截所有业务方法2.判断用户是否有权限,有权限就让他执行业务方法,没有权限就不允许执行.(是否有权限是根据user是否为null作为判断依据) 思考: 我们该如何实现? 思路1: 我们在每个 ...