flutter 異步任務(wù)隊(duì)列

隊(duì)列概念描述

我所講的任務(wù)隊(duì)列,是指異步的任務(wù)隊(duì)列,而不是代碼從上至下順序執(zhí)行后的按序執(zhí)行任務(wù).簡(jiǎn)單講幾個(gè)需求的場(chǎng)景
1.直播間禮物動(dòng)畫,直播間同時(shí)有5個(gè)人刷禮物,APP允許的最大同時(shí)存在禮物展示行數(shù)只允許2行,剩下3個(gè)就需要等待這兩人其中一人禮物播放結(jié)束,然后再進(jìn)入下一個(gè)人,先進(jìn)先出,如同排隊(duì)一樣
2.APP好友上線提醒,最多允許一個(gè),APP要求后上線好友的提醒必須等待之前的好友播放動(dòng)畫結(jié)束才可以展示(如果是那種直接覆蓋型的當(dāng)然是沒(méi)有這種問(wèn)題的)
還有非常非常多的需求場(chǎng)景,各位稍加思索,應(yīng)該就能想起這種隊(duì)列的必要性.

需求設(shè)計(jì)

Swift的異步任務(wù)隊(duì)列,可以通過(guò)原生的API DispatchQueue支持實(shí)現(xiàn),具體實(shí)現(xiàn)方式,我已在上次寫過(guò), 戳這,(不過(guò)swift的原生實(shí)現(xiàn)并不太優(yōu)雅,其實(shí)可以通過(guò)自定義operation的didChangeValue(forKey: "isFinished")等實(shí)現(xiàn),不過(guò)這是后話,后面有時(shí)間,我會(huì)寫一個(gè)更加優(yōu)雅的TaskProtocol),這里,我要講的是,flutter的任務(wù)隊(duì)列, dart原生沒(méi)有隊(duì)列相關(guān)的API,這里我們可以手動(dòng)實(shí)現(xiàn)一個(gè),接下來(lái)我們想想我們需要設(shè)計(jì)的功能需求:

  • 隊(duì)列并發(fā)數(shù)(允許任務(wù)同時(shí)執(zhí)行的任務(wù)數(shù)上限)
  • 隊(duì)列完成回調(diào)控制(控制每個(gè)任務(wù)何時(shí)完成,應(yīng)允許手動(dòng)控制)
  • 任務(wù)等待自動(dòng)取消(加入到任務(wù)隊(duì)列里的任務(wù)等待一段時(shí)間后自動(dòng)取消)
  • 任務(wù)等待自動(dòng)完成(執(zhí)行中的任務(wù)等待一段時(shí)間后自動(dòng)完成)
  • 任務(wù)權(quán)重,任務(wù)優(yōu)先級(jí)高的任務(wù)優(yōu)先插入到隊(duì)列前面

自動(dòng)取消,自動(dòng)完成都是我項(xiàng)目開(kāi)發(fā)中碰到的任務(wù)需求,簡(jiǎn)單舉幾個(gè)例子
自動(dòng)取消: 私聊消息提醒并不是一個(gè)非常重要的功能,上線后收到的一堆歷史消息,應(yīng)只展示最近的幾條,后續(xù)的消息提示展示,不用再展示給用戶看,所以后面的消息展示任務(wù)應(yīng)在等待一段時(shí)間后自動(dòng)消息掉.用戶可以前往消息列表自己查看
自動(dòng)完成: APP內(nèi)彈窗點(diǎn)擊,比如上線后,用戶有時(shí)候會(huì)收到一堆的彈窗點(diǎn)擊,簽到彈窗,青少年彈窗,引導(dǎo)彈窗等等,如果不加隊(duì)列控制,彈窗會(huì)一股腦全部彈出,我們希望用戶再處理完上次的彈窗點(diǎn)擊后,我們?cè)購(gòu)棾鱿乱粋€(gè)彈窗,但是,如果用戶一直不點(diǎn)擊彈窗,會(huì)導(dǎo)致后續(xù)累計(jì)的隊(duì)列任務(wù)越來(lái)越多,所以這里,可以增加一個(gè)自動(dòng)完成,在執(zhí)行A彈窗任務(wù)時(shí),等待一段時(shí)間,如果用戶不手動(dòng)完成,我們自動(dòng)完成掉A任務(wù),彈出B彈窗覆蓋在A彈窗之上,這樣就可以保證隊(duì)列中的任務(wù)不會(huì)被累積.

設(shè)計(jì)實(shí)現(xiàn)

我們對(duì)每一個(gè)執(zhí)行的任務(wù)都可以封裝成對(duì)象,并且,我們需要給予每一個(gè)任務(wù)有完成掉這個(gè)任務(wù)的能力,所以,任務(wù)的Function需要設(shè)計(jì)成這個(gè)樣子

///任務(wù)封裝
class TaskItem extends LinkedListEntry<TaskItem> {
  final TaskDetailFunc taskDetailFunc;
  final TaskCallback callback;
  final String uuid;

  /// 自動(dòng)完成該任務(wù)  任務(wù)正在進(jìn)行時(shí)
  final Duration? autoComplete;

  TaskItem({
    required this.uuid,
    required this.taskDetailFunc,
    required this.callback,
    this.autoComplete,
  });
}
typedef TaskCallback = void Function();
typedef TaskDetailFunc = void Function(TaskCallback taskCallback);

TaskDetailFunc是我們的任務(wù)對(duì)象,傳入一個(gè)TaskCallback的參數(shù),taskCallback的唯一作用,就是執(zhí)行callBack來(lái)結(jié)束掉該任務(wù),并且進(jìn)行一系列的判斷,開(kāi)啟下一個(gè)任務(wù)執(zhí)行, TaskFutureFuc是我們的外部任務(wù), TaskCallback是我們的內(nèi)部任務(wù)流轉(zhuǎn)邏輯,這么說(shuō)可能有點(diǎn)繞,實(shí)際上就是拋出一個(gè)結(jié)束標(biāo)志,讓外部在動(dòng)畫結(jié)束后調(diào)用.

參數(shù)定義

我們不需要定太多的邏輯,有用的參數(shù)僅僅下面幾個(gè),maxConcurrentOperationCount用于定義任務(wù)隊(duì)列最大并發(fā),_currentTaskCount用于控制并發(fā)數(shù),_isCanTaskRun用于判斷是否可以執(zhí)行下一個(gè)任務(wù),_taskList用于存儲(chǔ)我們需要的任務(wù)列表,如果要做細(xì)的話,甚至也可以定制最大的緩存任務(wù)數(shù),任務(wù)權(quán)重等,這個(gè)就由各位發(fā)揮了.

  int maxConcurrentOperationCount = 1;
 
  int _currentTaskCount = 0;

  bool get _isCanTaskRun => _currentTaskCount < maxConcurrentOperationCount;

  LinkedList<TaskItem> _taskList = LinkedList<TaskItem>();

初始化

TaskQueueUtil({required this.maxConcurrentOperationCount}) {
    assert(this.maxConcurrentOperationCount > 0, "? 任務(wù)并發(fā)數(shù)不能太小");
    assert(this.maxConcurrentOperationCount <= 5, "? 任務(wù)并發(fā)數(shù)不能太大");
  }

沒(méi)啥好說(shuō)的,隨便稍微限制一下

核心邏輯

我們?cè)O(shè)置一個(gè)addTask方法,在這個(gè)方法里,new出一個(gè)task對(duì)象,增加callBack邏輯判斷,調(diào)用執(zhí)行任務(wù),這里callBack回調(diào)可以稍微細(xì)說(shuō)一下: 受益于Completer的complete方法,當(dāng)一個(gè)Completer()被執(zhí)行complete后,后續(xù)再執(zhí)行complete會(huì)拋出異常,我們可以直接利用這一api特性,而省去自己寫防重復(fù)完成邏輯,這樣自動(dòng)完成,自動(dòng)取消邏輯都十分好寫了.

Future addTask(
    TaskDetailFunc futureFunc, {
    Duration? autoCancel,
    Duration? autoComplete,
  }) {
    Completer completer = Completer();

    String uuid = randomString();
    TaskItem taskItem = TaskItem(
      uuid: uuid,
      taskDetailFunc: futureFunc,
      autoComplete: autoComplete,
      callback: () {
        try {
          completer.complete();
        } catch (e) {
          psdllog("? 重復(fù)調(diào)用完成事件");
          return;
        }
        // 這里可能有問(wèn)題, 考慮要不要給事件+個(gè)id,通過(guò)id去移除這個(gè)事件
        // _taskList.removeWhere((element) => element.uuid == uuid);
        _currentTaskCount -= 1;
        //遞歸任務(wù)
        _doTask();
      },
    );
    _taskList.add(taskItem);
    _doTask();
    /*
    * 如果未執(zhí)行 自動(dòng)取消該任務(wù)
    * */
    if (autoCancel != null) {
      Future.delayed(autoCancel).then((value) {
        final bool isInTask = _taskList.contains(taskItem);
        if (isInTask) { // 不在任務(wù)列表的第一個(gè)就自動(dòng)取消掉該任務(wù)
          psdllog("? taskId: ${taskItem.uuid} 自動(dòng)取消任務(wù)");
          _taskList.remove(taskItem);
        }
      });

    }
    return completer.future;
  }

執(zhí)行下個(gè)任務(wù)

_doTask() async {
    if (!_isCanTaskRun) return;
    if (_taskList.isEmpty) return;

    //獲取先進(jìn)入的任務(wù)
    TaskItem task = _taskList.first;
    _taskList.remove(task);
    _currentTaskCount += 1;
    try {
      //執(zhí)行任務(wù)
      task.taskDetailFunc(task.callback);
      /*
      * 自動(dòng)完成該任務(wù)
      * */
      if (task.autoComplete != null) {
        Future.delayed(task.autoComplete!).then((value) {
          psdllog("? taskId: ${task.uuid} 自動(dòng)完成任務(wù)");
          task.callback.call();
        });
      }
    } catch (_) {
      task.callback.call();
    }
  }

剩余的注釋我都寫在代碼注釋里,由于我懶得寫任務(wù)權(quán)重,并且為了加快數(shù)組添加刪減,_taskList使用的是LinkedList定義,
任務(wù)這里還需要集成自class TaskItem extends LinkedListEntry<TaskItem> {

使用

for (var i in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) {
              taskQueue.addTask(
                (taskCallback) {
                  Future.delayed(Duration(seconds: 5)).then((value) {
                    psdllog("第$i次");
                    taskCallback.call();
                  });
                },
                autoCancel: Duration(seconds: 5),
                // autoComplete: Duration(seconds: 2),
              );
            }

結(jié)果:



各種自動(dòng)取消 自動(dòng)完成 手動(dòng)完成我均已測(cè)過(guò) 各位都可以自己去嘗試.

使用注意

如果你沒(méi)有用到自動(dòng)取消,自動(dòng)完成,那么taskCallback.call();一定要在邏輯結(jié)束時(shí)調(diào)用過(guò)一次,否則,整個(gè)隊(duì)列都會(huì)因?yàn)槟隳硞€(gè)任務(wù)的不調(diào)用taskCallback.call();而處在任務(wù)永遠(yuǎn)結(jié)束不了的情況,既然我把完成任務(wù)的權(quán)利交給了每個(gè)任務(wù),這每個(gè)任務(wù)就有責(zé)任必須調(diào)用到taskCallback.call(),我在代碼中加入了如果task.taskDetailFunc(task.callback);出現(xiàn)執(zhí)行異常,會(huì)執(zhí)行掉任務(wù)的完成,但是如果你的某個(gè)彈窗,想用這個(gè)隊(duì)列,但是又不執(zhí)行call(),就會(huì)出問(wèn)題.

PS

致謝: Flutter:使用Completer實(shí)現(xiàn)自定義任務(wù)隊(duì)列,是這篇文章給予了我初期思路
下一篇暫時(shí)沒(méi)想好寫什么,可能是flutter音視頻項(xiàng)目的總結(jié),也可能是Swift相關(guān),之前swift項(xiàng)目系列立了很多的flag,都沒(méi)實(shí)現(xiàn),唉~~~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容