Async*和yield的应用:制作一个简单的异步任务管理器

前言

在开发过程中,我们经常会遇到这样的需求:有一批任务,每个任务的处理时长均不相同。当开始处理这批任务时,希望顺序执行,并展示总体进度和当前处理的任务。 如下图:

设计和实现

通过上面的需求描述,我们不难发现此模块需要两个类来实现:异步任务(AsyncTask)和任务管理器(AsyncTaskManager)。其类图及实现如下:

AsyncTask

classDiagram class AsyncTask{ +ValueNotifier progress +get describe() +dispose() +startTask() }

根据类图,我们可以设计如下抽象类:

csharp 复制代码
abstract class AsyncTask {

  ///当前任务执行进度 [0-1.0]
  final ValueNotifier<double> progress = ValueNotifier(0.0);

  ///任务描述
  String get describe;

  ///释放任务
  void dispose();

  ///开始任务
  Future<bool> startTask();

}

AsyncTaskManager

classDiagram class AsyncTaskManager{ +bool strict -ValueNotifier progress -ValueNotifier taskDescribe -double taskWeight -int totalToastLen -Queue taskQueue -Completer signal +registerTask(AsyncTask task) +dispose() +getTaskByType(): T? +launchTask():Future -watchTask(AsyncTask t) -fetchTask():Stream }

这其中,我们将管理器内部的执行分为两部分:取任务_fetchTask()和执行任务launchTask()。由于二者不存在顺序关系,为了实现"顺序执行"这个需求,我们首先通过async*yield两个关键字,将任务的获取转换为Stream,之后增加一个signal(Completer)当作信号,每次取出一个任务时,便等待信号的变化,任务处理完成后,发送信号以获取下一个任务。实现如下:

ini 复制代码
class AsyncTaskManager {

  AsyncTaskManager({this.strict = false});

  ///严格模式
  /// * true : 一个任务错误,中断整个任务队列
  final bool strict;

  ///执行进度
  final _progress = 0.0.vn;
  ValueNotifier<double> get progress => _progress;

  ///任务描述
  final _taskDescribe = ''.vn;
  ValueNotifier<String> get taskDescribe => _taskDescribe;

  ///每个任务的进度权重
  /// * 影响进度的迭代值
  double _taskWeight = 1.0;
  
  ///初始任务总数量
  int _totalTaskLen = 0;

  Completer<bool>? _signal;

  ///待处理的任务队列
  final _taskQueue = Queue<AsyncTask>();

  ///注册任务
  void registerTask(AsyncTask task) {
    _taskQueue.add(task);
    _taskWeight = 1 / _taskQueue.length;
  }

  ///根据类型返回指定任务
  T? getTaskByType<T extends AsyncTask>() {
    try{
      final t = _taskQueue.firstWhere((element) => element is T);
      return t as T;
    } catch(e) {
      debugPrint(e.toString());
      return null;
    }
  }


  ///释放全部任务
  void dispose() {
    while(_taskQueue.isNotEmpty) {
      final t = _taskQueue.removeFirst();
      t.dispose();
    }
  }


  ///启动任务
  ///返回 任务链是否执行成功
  Future<bool> launchTask() async {
    _totalTaskLen = _taskQueue.length;
    final reporter = Completer<bool>();
    _fetchTask().listen((t) async {
      _watchTask(t);
      final success = await t.startTask();
      if(strict && !success) {
        _signal?.complete(false);
        reporter.complete(false);
        return;
      }
      _signal?.complete(true);
      _signal = null;
      if(_taskQueue.isEmpty) reporter.complete(true);
    });
    return reporter.future;
  }

  void _watchTask(AsyncTask t) {
    void updateTask() {
      final p = t.progress.value / _totalTaskLen
                + (_totalTaskLen - _taskQueue.length - 1)
                * _taskWeight;
      _progress.value = p;
      if(t.progress.value == 1) {
        t.progress.removeListener(updateTask);
      }
    }
    t.progress.addListener(updateTask);
  }

  Stream<AsyncTask> _fetchTask() async* {
    while(_taskQueue.isNotEmpty) {
      final task = _taskQueue.removeFirst();
      _taskDescribe.value = task.describe;
      _signal = Completer();
      yield task;
      final proceed = await _signal!.future;
      //信号异常,终止取出
      if(!proceed) return;
    }
  }


}

///---------便捷性拓展--------
extension NumExt on num{

  ValueNotifier<double> get vn => ValueNotifier(toDouble());

}

extension StrExt on String{

  ValueNotifier<String> get vn => ValueNotifier(this);

}

至此,一个异步任务管理器便完成了,如果有不足的地方欢迎指出交流,谢谢。

其他文章

Flutter------ 一个有意思的工具:行为录制器 behavior_recorder

Flutter插件------简洁实用的图片编辑器 - 掘金 (juejin.cn)

Flutter&Flame在游戏上的实践------坦克大战

Flutter------原生View的Touch事件分发流程

Flutter 仿同花顺自选股列表

相关推荐
专业掘金1 分钟前
0429 手打基础丸
前端
还有发量的前端程序员1 分钟前
Vue3初始化完整过程和原理
前端
Jenlybein2 分钟前
进阶学习 Javascript ? 来看看这篇系统复习笔记 [ Generator 篇 ]
前端·javascript·面试
绅士玖5 分钟前
Vue.js 小知识点大揭秘:提升开发效率的实用技巧
前端·vue.js
彭不懂赶紧问5 分钟前
鸿蒙NEXT开发浅进阶到精通04:类似支付宝横向导航栏与list组件联动随航
前端·harmonyos
Jenlybein5 分钟前
进阶学习 Javascript ? 来看看这篇系统复习笔记 [ Promise 与 async 篇 ]
前端·javascript·面试
用户2031196600967 分钟前
SwiftUI 中的 scaleEffect 修饰器
前端
袁煦丞10 分钟前
Mdserver-web让服务器自由飞翔!:cpolar内网穿透实验室第590个成功挑战
前端·程序员·远程工作
初心_202410 分钟前
2. python协程/异步编程详解
java·前端·python
快乐就是哈哈哈25 分钟前
new Map 这么好用,你为什么不用?是不喜欢吗?🌸
前端