Dart中如何优雅实现可随时终止的长耗时异步函数
Dart 可取消异步任务实现方案
原有方案的问题根因
你之前采用的全局布尔标记、裸封装CancelableOperation的写法存在本质缺陷:
- 全局共享的
stopRun变量是多实例并发的天然阻碍,且每个异步步骤手动写if(stopRun) return侵入性极强,后续新增/调整步骤很容易漏写判断,维护成本极高 - 取消信号没有传递到
doX()/doY()/doZ()等子任务内部,触发终止后必须等当前正在执行的子任务跑完才能退出,做不到即时响应 - 直接用
CancelableOperation.fromFuture包裹已有Future的写法,本质只是丢弃了Future的结果监听,Future内部的执行逻辑完全不会被终止,属于典型的"伪取消"
可落地的实现方案
Dart 本身没有提供强制终止异步任务的顶层API,但通过取消令牌的设计模式,可以完美解决你提到的三个问题,以下是两种生产环境常用的实现:
方案1:轻量自实现取消令牌(无第三方依赖)
核心思路是把全局共享的停止标记封装成任务实例独有的令牌对象,抽离重复的取消判断逻辑,同时让子任务可以感知取消信号:
/// 单任务独有的取消令牌,每个任务实例单独创建,天然支持多并发 class CancelToken { bool _isCancelled = false; bool get isCancelled => _isCancelled; final Completer _cancelCompleter = Completer.sync(); /// 取消事件触发的Future,供子任务做即时中断监听 Future get whenCancel => _cancelCompleter.future; /// 触发取消 void cancel() { if (_isCancelled) return; _isCancelled = true; _cancelCompleter.complete(); } } /// 抽离公共取消检查逻辑,避免每步重复写判断 extension CancelTokenCheck on CancelToken { Future<T> runCancellable<T>(Future<T> Function() task) async { if (_isCancelled) throw StateError('任务已取消'); // 同时执行子任务和监听取消信号,任意一个先触发就立即返回 final result = await Future.any<T>([ task(), whenCancel.then((_) => throw StateError('任务已取消')) ]); if (_isCancelled) throw StateError('任务已取消'); return result; } }
改造后的run方法逻辑非常简洁,后续新增步骤不需要重复写判断:
Future run(CancelToken token) async { await token.runCancellable(doX); await token.runCancellable(doY); await token.runCancellable(doZ); // 新增步骤直接套runCancellable即可 }
多实例调用方式:
// 启动两个完全独立的任务,互不干扰 final task1Token = CancelToken(); run(task1Token); final task2Token = CancelToken(); run(task2Token); // 随时取消任意任务,不需要等当前子任务执行完 task1Token.cancel();
方案2:基于官方async包的CancelableOperation实现(生产推荐)
官方维护的async包提供了开箱即用的可取消操作封装,不需要自己手写取消令牌,适合生产环境使用。注意你之前的用法是错误的,必须在任务逻辑内部主动响应取消状态,才能实现真正的终止:
import 'package:async/async.dart'; /// 创建独立的可取消任务 CancelableOperation createRunTask() { late final CancelableOperation operation; operation = CancelableOperation.fromFuture( Future(() async { await doX(); if (operation.isCanceled) return; await doY(); if (operation.isCanceled) return; await doZ(); if (operation.isCanceled) return; }), onCancel: () { // 这里写取消后的资源回收逻辑:关闭文件句柄、断开网络连接等 }, ); return operation; }
如果需要实现子任务执行过程中的即时中断,可以参考方案1的Future.any逻辑,在子任务执行时同时监听operation.value或者自定义取消回调即可。
注意事项
Dart 单线程事件循环模型的特性决定了:没有办法从外部强制终止一段正在运行的同步CPU密集代码(比如无检查点的长循环、大数值计算)。如果你的
doX/doY/doZ属于这类场景,要么在计算逻辑内部每隔固定步数加一次取消检查,要么把CPU密集逻辑放到独立Isolate中运行,取消时直接终止Isolate才能做到真正的即时中断。
如果是IO类任务(网络请求、文件读写),大部分官方IO库都支持传入取消信号,触发取消时会直接中断底层IO连接,不需要等待操作自然完成。
内容的提问来源于stack exchange,提问作者flutter_rowen
相关产品推荐
相关产品推荐

