Flutter 中基于 Future 的任务队列实现:支持去重与条件触发

桃夭
桃夭
发布时间:2026-10-15| 6人浏览过
原创

在 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();
    }
  }
}

关键点:

  1. 状态锁 dealing:防止在任务执行期间再次调用 startLoop 导致重叠执行。
  2. 递归调用:在 finally 块中检查队列非空后再次调用自身,形成链式执行。
  3. 异常捕获:即使任务出错,也会清理当前任务并继续尝试后续任务,保证队列不被卡死。

执行条件控制

在实际场景中(如仅在首页显示新人引导),任务可能需要满足特定条件才执行。我们引入 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 状态管理的资料请关注本站其它相关文章!

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。