Flutter 中基于 Future 的任务队列实现:支持去重与条件触发
在 Flutter 开发中,处理多个异步 UI 操作(如连续弹窗)时,需确保任务按顺序执行且互不冲突。本文详解一种基于 Dart Future 的任务队列实现方案,支持任务去重、指定 ID 管理及执行条件判断,适用于电商首页引导、广告依次展示等场景。
核心数据结构设计
为了实现任务的管理,首先需要定义任务类型及底层存储结构。任务本身是一个返回 Future<T> 的函数,以便能够等待其完成(例如等待用户关闭弹窗)后再执行下一步。
typedef TaskEventFunction = Future Function();
/// 任务编号队列:维护执行顺序
List<String> _actionQueue = [];
/// 任务映射表:根据 ID 获取具体任务函数
Map<String, TaskEventFunction> _actionMap = {};
引入两个独立的集合:_actionQueue 用于记录任务的执行顺序和去重检查,_actionMap 用于根据 ID 快速查找并执行对应的回调函数。这种分离使得我们可以在不去除任务函数的情况下,仅通过 ID 管理队列状态。
第1步:实现任务添加与去重逻辑
添加任务时,默认使用函数的 hashCode 作为唯一标识。对于匿名闭包(Lambda),每次定义都会生成不同的实例,因此 hashCode 不同,会导致重复入队。为解决此问题,提供 taskId 参数允许显式指定 ID;若指定为 '-1',则强制不去重。
String add(TaskEventFunction task, {
String? taskId,
}) {
// 确定唯一标识:优先使用传入 taskId,其次使用 hashCode,'-1' 代表不去重
String id = taskId ?? (taskId == '-1' ? '' : task.hashCode.toString());
bool isContains = false;
if (taskId != '-1') {
isContains = _actionQueue.contains(id);
}
if (!isContains) {
_actionQueue.add(id);
_actionMap[id] = task;
}
return id;
}
该方法的返回值是 taskId,调用者可利用此 ID 进行后续的移除或状态查询操作。
第2步:实现任务移除功能
支持通过原始任务对象或明确的 taskId 移除队列中的任务。若使用对象移除,系统会自动计算其 hashCode 进行匹配。
bool remove(TaskEventFunction task, {String? taskId}) {
String id = taskId ?? task.hashCode.toString();
if (_actionQueue.contains(id)) {
_actionMap.remove(id);
return _actionQueue.remove(id);
}
return false;
}
任务执行循环控制
队列的核心在于“执行当前任务,完成后自动触发下一个”。由于任务通常是异步的(如等待网络请求或用户交互),需使用 async/await 配合状态锁防止并发执行。
bool dealing = false; // 状态锁:标记是否正在处理任务
void startLoop() async {
// 如果正在处理或队列为空,直接返回
if (dealing || _actionQueue.isEmpty) {
return;
}
dealing = true;
String taskId = _actionQueue.first;
TaskEventFunction? callback = _actionMap[taskId];
if (callback == null) {
// 任务不存在则清理引用
_actionQueue.remove(taskId);
dealing = false;
return;
}
try {
await callback(); // 等待当前任务完成(如弹窗关闭)
} catch (e) {
log('_actionQueue Error: $e');
} finally {
// 清理已完成的任务
_actionQueue.remove(taskId);
_actionMap.remove(taskId);
dealing = false;
// 递归触发下一个任务
if (_actionQueue.isNotEmpty) {
startLoop();
}
}
}
关键点:
- 状态锁
dealing:防止在任务执行期间再次调用startLoop导致重叠执行。 - 递归调用:在
finally块中检查队列非空后再次调用自身,形成链式执行。 - 异常捕获:即使任务出错,也会清理当前任务并继续尝试后续任务,保证队列不被卡死。
执行条件控制
在实际场景中(如仅在首页显示新人引导),任务可能需要满足特定条件才执行。我们引入 TaskConditionFunction 来扩展队列能力。
typedef TaskConditionFunction = FutureOr<bool> Function();
Map<String, TaskConditionFunction> _actionCondition = {};
TaskConditionFunction? _globalCondition;
第3步:绑定任务条件
在添加任务时可传入 condition,也可通过 setAcceptConditions 单独设置。
TaskEventFunction add(TaskEventFunction task, {
String? taskId,
TaskConditionFunction? condition,
}) {
String id = add(task, taskId: taskId); // 复用之前的添加逻辑
if (condition != null && !_actionCondition.containsKey(id)) {
_actionCondition[id] = condition;
}
return id;
}
void setAcceptConditions(TaskConditionFunction condition, {String? taskId}) {
if (taskId == null) {
_globalCondition = condition;
} else {
_actionCondition[taskId] = condition;
}
}
第4步:条件检查集成
在执行任务前,必须验证总条件和任务自身条件。两者均需满足时方可执行。
Future<bool> _checkConditions(String taskId) async {
bool canExecute = true;
// 1. 检查全局条件
if (_globalCondition != null) {
canExecute = await _globalCondition!();
}
// 2. 检查任务特定条件
if (canExecute && _actionCondition.containsKey(taskId)) {
canExecute = await _actionCondition[taskId]!();
}
return canExecute;
}
将上述检查集成到 startLoop 中:
if (await _checkConditions(taskId)) {
try {
await callback();
} catch (e) {
log('Error: $e');
} finally {
// ... 清理逻辑 ...
if (_actionQueue.isNotEmpty) startLoop();
}
} else {
// 条件不满足:清除当前任务,并根据需求决定是否继续后续任务
// 此处示例为中断整个队列
dealing = false;
_actionQueue.remove(taskId);
_actionMap.remove(taskId);
}
实际使用示例
以下代码演示如何创建一个全局任务队列,并在首页加载时依次执行多个弹窗操作。
// 1. 初始化
TaskQueue taskQueue = TaskQueue();
// 2. 定义任务
Future showWelcomeDialog() async {
final result = showDialog(
context: appContext,
builder: (ctx) => WelcomeDialog(),
);
// 等待用户点击关闭
await result;
}
// 3. 配置任务与条件
final taskId = taskQueue.add(showWelcomeDialog, condition: () {
return isFirstVisit; // 假设的全局变量
});
taskQueue.add(showPromotionDialog, condition: () {
return hasPermission;
});
// 4. 启动执行
taskQueue.startLoop();
通过这种方式,可以实现复杂但有序的用户引导流程。若需跨页面共享任务状态,应使用单例模式的 TaskQueue 实例;若仅为当前页面服务,则在 Widget 中初始化即可。
以上就是 Flutter 任务队列实现的详细内容,更多关于 Flutter 异步编程与 UI 状态管理的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









