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


【你不一定知晓的】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_酱


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM