隊(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),唉~~~