【你不一定知晓的】C#取消异步操作

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

 早期

早期.Net 使用 BackgroundWorker 完成异步长时间运行操作。
可以使用CacnelAsync方法设置 CancellationPending = true
private void BackgroundLongRunningTask(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = (BackgroundWorker)sender; for (int i = 1; i <= 10000; 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(() =>
{
decimal result = 0; // Loop for a defined number of iterations
for (int i = 0; i < loop; i++)
{
// Do something that takes times like a Thread.Sleep in .NET Core 2.
Thread.Sleep(10);
result += i;
} return result;
});
}
// 这里我们使用Thread.Sleep 模仿长时间运行的操作

简单异步调用代码:

public static async Task ExecuteTaskAsync()
{
Console.WriteLine(nameof(ExecuteTaskAsync));
Console.WriteLine("Result {0}", await LongRunningOperation(100));
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(() =>
{
decimal result = 0; // Loop for a defined number of iterations
for (int i = 0; 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(10);
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
{
var result = await LongRunningCancellableOperation(500, 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
{
var longRunningTask = LongRunningCancellableOperation(500, 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
var longRunningTask = LongRunningOperationWithCancellationTokenAsync(100, 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_酱

【你不一定知晓的】C#取消异步操作的更多相关文章

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

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

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

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

  3. 使用BackgroundWorker组件进行异步操作编程

    本文介绍了BackgroundWorker组件的功能及在基于事件的异步操作编程中的应用,并对组件的实现原理进行简述.在应用程序中,可能会遇到一些执行耗时的功能操作,比如数据下载.复杂计算及数据库事务等 ...

  4. [Web] 取消Promise

    转载自 为Promise插上可取消的翅膀 const makeCancelable = (promise) => { let hasCanceled_ = false; const wrappe ...

  5. 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 ...

  6. C#多线程之线程池篇2

    在上一篇C#多线程之线程池篇1中,我们主要学习了如何在线程池中调用委托以及如何在线程池中执行异步操作,在这篇中,我们将学习线程池和并行度.实现取消选项的相关知识. 三.线程池和并行度 在这一小节中,我 ...

  7. [C#] 走进异步编程的世界 - 剖析异步方法(上)

    走进异步编程的世界 - 剖析异步方法(上) 序 这是上篇<走进异步编程的世界 - 开始接触 async/await 异步编程>(入门)的第二章内容,主要是与大家共同深入探讨下异步方法. 本 ...

  8. Future和Promise

    Future用于获取异步操作的结果,而Promise则比较抽象,无法直接猜测出其功能. Future Future最早来源于JDK的java.util.concurrent.Future,它用于代表异 ...

  9. Event-based Asynchronous Pattern Overview基于事件的异步模式概览

    https://msdn.microsoft.com/zh-cn/library/wewwczdw(v=vs.110).aspx Applications that perform many task ...

随机推荐

  1. ng2-tree

    [转]https://github.com/valor-software/ng2-tree#eyes-demo  demo:http://valor-software.com/ng2-tree/

  2. 12.1-uC/OS-III调度的内部实现

    1.调度的内部实现通过这两个函数完成调度功能: OSSched()和OSIntExit().OSSched()在任务级被调用, OSIntExit()在中断级被调用.这两个函数都在OS_CORE.C中 ...

  3. VoiceXML简介

    简单来说,VoiceXML就是语音网络世界的HTML,一种用于语音应用的开放标准的标记语言.VoiceXML的问世使得为HTML发展起来的web体系也能够轻松地创建和使用语音应用. 发展历史: 199 ...

  4. POJ 1986 Distance Queries(LCA Tarjan法)

    Distance Queries [题目链接]Distance Queries [题目类型]LCA Tarjan法 &题意: 输入n和m,表示n个点m条边,下面m行是边的信息,两端点和权,后面 ...

  5. Hbase 读写 原理

    客户端读取信息流程 ()client要读取信息,先查询下client 端的cache中是否存在数据,如果存在,刚直接返回数据.如果不存在,则进入到zookeeper,查找到里面的相应数据存在的Root ...

  6. python-17

    # 列表生成式 a = [x*2 for x in range(10)] # 这两个变量必须一致 print(a) #列表 元组的高级赋值办法 b,c = [",6] # python的垃圾 ...

  7. 用命令行打开sublime

    在linux下装了linux后默认并不能通过运行命令的方式打开,这就让我们不能像vim一样可以通过 vim <fileName> 来打开文件. 不过我们可以通过把sublime的执行文件放 ...

  8. [openjudge-动态规划]Maximum sum

    题目描述 题目原文 描述 Given a set of n integers: A={a1, a2,-, an}, we define a function d(A) as below: d(A)=m ...

  9. flask 定义数据库关系(一对一)

    一对一 我们将使用国家和首都来演示一对一关系:每个国家只有一个首都.反过来,一个城市也只能作为一个国家的首都.一对一关系如下: 在示例程序中,Country类表示国家,Capital类表示首都.建立一 ...

  10. LeetCode169 求众数

    题目链接:https://leetcode-cn.com/problems/majority-element/ 给定一个大小为 n 的数组,找到其中的众数.众数是指在数组中出现次数大于 ⌊ n/2 ⌋ ...