多線程任務(wù)開發(fā)范例-TaskPool
想了解更多關(guān)于開源的內(nèi)容,請?jiān)L問:
概念介紹
任務(wù)池(taskpool)為應(yīng)用程序提供一個(gè)多線程的運(yùn)行環(huán)境,可以使用任務(wù)池API創(chuàng)建后臺(tái)任務(wù)(Task),并對(duì)所創(chuàng)建的任務(wù)進(jìn)行如任務(wù)執(zhí)行、任務(wù)取消的操作。使用任務(wù)池,無需關(guān)心線程實(shí)例的生命周期,提升開發(fā)體驗(yàn),還能降低整體資源的消耗、提高系統(tǒng)的整體性能。
API接口
任務(wù)池模塊提供的功能非?;A(chǔ),只支持任務(wù)構(gòu)造,任務(wù)執(zhí)行,任務(wù)取消等3類接口。關(guān)于任務(wù)池(taskpool)API能力詳細(xì)信息,請參考@ohos.taskpool。本節(jié)只進(jìn)行關(guān)鍵接口解讀。
Task構(gòu)造函數(shù)
使用線程池運(yùn)行后臺(tái)任務(wù)前,可以先構(gòu)造一個(gè)Task實(shí)例。Task構(gòu)造函數(shù)如下:
constructor(func: Function, …args: unknown[])
其中,參數(shù)解釋:
參數(shù)名 | 類型 | 必填 | 說明 |
func | Function | 是 | 任務(wù)執(zhí)行需要傳入函數(shù),支持的函數(shù)返回值類型請查序列化支持類型。 |
args | unknown[] | 否 | 任務(wù)執(zhí)行傳入函數(shù)的參數(shù),支持的參數(shù)類型請查序列化支持類型。默認(rèn)值為undefined。 |
我們來看一個(gè)構(gòu)造的示例。
@Concurrent
function printArgs(args) {
console.log("printArgs: " + args);
return args;
}
let task = new taskpool.Task(printArgs, "this is my first Task");
注意:上述實(shí)例代碼是官方API提供的,實(shí)際上并不可用,使用裝飾器@Concurrent會(huì)報(bào)錯(cuò)。實(shí)際上使用的是如下代碼:
function concurrentTask(durationMs: number) {
"use concurrent"
console.info("[concurrentTask] task start, the args is " + durationMs)
setTimeout(() => {
console.info("[concurrentTask] task end ")
}, durationMs)
console.info("[concurrentTask] task return")
return '[concurrentTask] returned'
}
function createTask() {
let task = new taskPool.Task(concurrentTask, 10 * 1000)
console.info("[createTask] created successfully")
return task
}
執(zhí)行異步函數(shù)
將待執(zhí)行的函數(shù)放入taskpool內(nèi)部任務(wù)隊(duì)列等待,等待分發(fā)到工作線程執(zhí)行。此種方式執(zhí)行的情況下,沒有創(chuàng)建任務(wù),所以不可取消任務(wù)。后文將介紹的taskpool.cancel函數(shù)需要傳入taskpool.Task參數(shù)。
接口定義如下,其中的參數(shù)不再解釋,和Task構(gòu)造函數(shù)的參數(shù)一樣。
execute(func: Function, …args: unknown[]): Promise<unknown>
示例代碼如下:
@Concurrent
function printArgs(args) {
console.log("printArgs: " + args);
return args;
}
async function taskpoolExecute() {
let value = await taskpool.execute(printArgs, 100);
console.log("taskpool result: " + value);
}
...
taskpoolExecute();
執(zhí)行Task任務(wù)
將創(chuàng)建好的任務(wù)放入taskpool任務(wù)池里等待,等待分發(fā)到工作線程執(zhí)行。當(dāng)前執(zhí)行模式可嘗試調(diào)用cancel進(jìn)行任務(wù)取消。接口定義如下:
execute(task: Task, priority?: Priority): Promise<unknown>
其中,參數(shù)如下:
參數(shù)名 | 類型 | 必填 | 說明 |
task | 是 | 需要在任務(wù)池中執(zhí)行的任務(wù)。 | |
priority | 否 | 等待執(zhí)行的任務(wù)的優(yōu)先級(jí)(暫未支持)。 |
示例代碼如下:
@Concurrent
function printArgs(args) {
console.log("printArgs: " + args);
return args;
}
async function taskpoolExecute() {
let task = new taskpool.Task(printArgs, 100);
let value = await taskpool.execute(task);
console.log("taskpool result: " + value);
}
taskpoolExecute();
取消Task任務(wù)
取消任務(wù)池中的任務(wù)。在Task構(gòu)造實(shí)例后直接調(diào)用cancal接口會(huì)找不到要取消的任務(wù),需要調(diào)用execute接口后,才會(huì)放入任務(wù)池,調(diào)用cancel接口才有意義。
接口定義如下:
cancel(task: Task): void
其中,參數(shù)如下:
參數(shù)名 | 類型 | 必填 | 說明 |
task | 是 | 需要取消執(zhí)行的任務(wù)。 |
示例代碼如下:
@Concurrent
function printArgs(args) {
console.log("printArgs: " + args);
return args;
}
async function taskpoolCancel() {
let task = new taskpool.Task(printArgs, 100);
taskpool.execute(task);
try {
taskpool.cancel(task);
} catch (e) {
console.log("taskpool.cancel occur error:" + e);
}
}
taskpoolCancel();
實(shí)現(xiàn)場景
我們主要為了體驗(yàn)線程池的使用,實(shí)現(xiàn)任務(wù)創(chuàng)建、任務(wù)執(zhí)行和任務(wù)取消的功能。為了簡化,相關(guān)輸出使用console控制輸出即可。
設(shè)計(jì)思路
簡化界面實(shí)現(xiàn),只需要簡單地包含一個(gè)text和三個(gè)button。text用于展示接口調(diào)用信息,不同的button按鈕被點(diǎn)擊后觸發(fā)調(diào)用不同的接口。通過設(shè)置日志查看操作執(zhí)行情況。
開發(fā)步驟
UI界面實(shí)現(xiàn)
代碼非常簡單,使用DevEco Studio創(chuàng)建一個(gè)Empty Ability空工程后,加3個(gè)按鈕就行。
“Create Task"按鈕會(huì)創(chuàng)建一個(gè)任務(wù)。創(chuàng)建的Task實(shí)例會(huì)賦值給組件的變量,創(chuàng)建任務(wù)的代碼如下?,F(xiàn)在存在一個(gè)問題需要確認(rèn),@concurrent裝飾器不知道如何使用,
當(dāng)前使用的"use concurrent”。需要確認(rèn)官方文檔是否存在問題。
function concurrentTask(durationMs: number) {
"use concurrent"
console.info("[concurrentTask] task start, the args is " + durationMs)
setTimeout(() => {
console.info("[concurrentTask] task end ")
}, durationMs)
console.info("[concurrentTask] task return")
return '[concurrentTask] returned'
}
function createTask() {
let task = new taskPool.Task(concurrentTask, 10 * 1000)
console.info("[createTask] created successfully")
return task
}
"Execute Task"按鈕會(huì)執(zhí)行一個(gè)任務(wù)。組件的變量持有任務(wù),該按鈕可以持續(xù)點(diǎn)擊,反復(fù)執(zhí)行任務(wù)。
"Cancel Task"按鈕會(huì)取消一個(gè)任務(wù)。如果認(rèn)為沒有調(diào)用execute接口,不在線程池里,取消會(huì)阿伯錯(cuò);如果任務(wù)正在執(zhí)行中,再去取消也會(huì)報(bào)錯(cuò)。實(shí)際上運(yùn)行,沒有得到想要的效果,需要進(jìn)一步確認(rèn)。
UI代碼
entry\src\main\ets\pages\Index.ets文件片段如下:
task: taskPool.Task = null
...
Column() {
Text(this.message)
.fontSize(20)
.fontWeight(FontWeight.Bold)
Blank()
Row() {
Button("Create Task").onClick(() => {
this.task = createTask()
this.message = "Task Created"
})
Button("Execute Task").onClick(() => {
executeTask(this.task)
this.message = "Task Executed"
})
Button("Cancel Task").onClick(() => {
cancelTask(this.task)
this.message = "Task Cancel involved"
})
}
}
執(zhí)行任務(wù)代碼實(shí)現(xiàn)
我們再執(zhí)行任務(wù)代碼如何實(shí)現(xiàn)。
我們從上文知道,taskPool.execute接口有兩種方式,可以傳入異步函數(shù)和參數(shù),也可以傳入task實(shí)例和優(yōu)先級(jí)參數(shù)。
此處,我們使用第二種方法,傳入task實(shí)例和優(yōu)先級(jí)參數(shù)。任務(wù)池模塊尚未支持優(yōu)先級(jí),該參數(shù)也可以省略。
執(zhí)行任務(wù)代碼片段如下:
function executeTask(task: taskPool.Task) {
console.info("[executeTask] executing")
try {
taskPool.execute(task, taskPool.Priority.HIGH).then(() => {
console.info("[executeTask] executed successfully")
})
} catch (e) {
console.error("[executeTask] execute failed, " + e.toString())
}
}
取消任務(wù)代碼實(shí)現(xiàn)
我們再看下取消任務(wù)的代碼如何實(shí)現(xiàn)。
如果沒有通過調(diào)用execute接口,沒有放入任務(wù)池的task,執(zhí)行cancel函數(shù)時(shí),會(huì)報(bào)異常,畢竟在線程池是不存在的。
然后模擬一個(gè)延時(shí)操作,再去調(diào)用cancel函數(shù)。
期望可以正常取消任務(wù),實(shí)際上,任務(wù)沒有被取消,也沒有報(bào)異常。這里就很奇怪,需要繼續(xù)調(diào)查原因。
取消任務(wù)代碼片段:
function cancelTask(task: taskPool.Task) {
console.info("[cancelTask] canceling ")
try {
taskPool.execute(task, taskPool.Priority.HIGH)
let start = new Date().getTime()
// 延時(shí)1s,確保任務(wù)已執(zhí)行
while (new Date().getTime() - start < 1000) {
continue
}
taskPool.cancel(task)
} catch (e) {
console.error("[cancelTask] cancel failed" + e.toString())
}
}
運(yùn)行測試效果
代碼編寫完畢,可以測試運(yùn)行查看效果。推薦在模塊級(jí)配置文件entry\build-profile.json5
中,修改運(yùn)行時(shí)為"HarmonyOS",這樣就可以在DevEco Studio中使用Simulator模擬器進(jìn)行運(yùn)行測試,手頭沒有設(shè)備也可以輕松體驗(yàn)OpenHarmony應(yīng)用開發(fā)。分別點(diǎn)擊按鈕,日志輸出如下:
07-30 22:28:28.357 4214-7232/com.example.taskpooldemo I 0FEFE/JsApp: [createTask] created successfully
07-30 22:28:30.576 4214-7232/com.example.taskpooldemo I 0FEFE/JsApp: [executeTask] executing
07-30 22:28:30.576 4214-7256/com.example.taskpooldemo I 0FEFE/JsApp: [concurrentTask] task start, the args is 10000
07-30 22:28:30.576 4214-7256/com.example.taskpooldemo I 0FEFE/JsApp: [concurrentTask] task return
07-30 22:28:30.577 4214-7232/com.example.taskpooldemo I 0FEFE/JsApp: [executeTask] executed successfully
07-30 22:28:37.602 4214-7232/com.example.taskpooldemo I 0FEFE/JsApp: [cancelTask] canceling
07-30 22:28:37.608 4214-7256/com.example.taskpooldemo I 0FEFE/JsApp: [concurrentTask] task start, the args is 10000
07-30 22:28:37.609 4214-7256/com.example.taskpooldemo I 0FEFE/JsApp: [concurrentTask] task return
07-30 22:28:38.607 4214-7232/com.example.taskpooldemo E 0FEFE/JsApp: [cancelTask] cancel failedBusinessError: The task does not exist when it is canceled, taskpool:: can not find the task
07-30 22:28:40.580 4214-7256/com.example.taskpooldemo I 0FEFE/JsApp: [concurrentTask] task end
07-30 22:28:47.608 4214-7256/com.example.taskpooldemo I 0FEFE/JsApp: [concurrentTask] task end
注意事項(xiàng)
理論上,您可以使用任務(wù)池API創(chuàng)建數(shù)量不受限制的任務(wù)。當(dāng)同一時(shí)間待執(zhí)行的任務(wù)數(shù)量大于任務(wù)池工作線程數(shù)量,任務(wù)池會(huì)根據(jù)負(fù)載均衡機(jī)制進(jìn)行擴(kuò)容,增加工作線程數(shù)量,減少整體等待時(shí)長。同樣,當(dāng)執(zhí)行的任務(wù)數(shù)量減少,工作線程數(shù)量大于執(zhí)行任務(wù)數(shù)量,部分工作線程處于空閑狀態(tài),任務(wù)池會(huì)根據(jù)負(fù)載均衡機(jī)制進(jìn)行縮容,減少工作線程數(shù)量。遺憾的是,負(fù)載均衡機(jī)制暫未支持。
創(chuàng)建的同一優(yōu)先級(jí)任務(wù)的執(zhí)行順序可以由您決定,任務(wù)真實(shí)執(zhí)行的順序與您調(diào)用任務(wù)池API提供的任務(wù)執(zhí)行接口順序一致。任務(wù)默認(rèn)優(yōu)先級(jí)是taskPool.Priority.MEDIUM。遺憾的是,任務(wù)優(yōu)先級(jí)機(jī)制暫未支持,可以忽略。
@concurrent裝飾器如何使用,需要繼續(xù)確認(rèn)。另外,官方文檔中提到:僅支持在Stage模型且module的compileMode為esmodule的project中使用taskpool api。compileMode設(shè)置為什么看起來并不影響什么,需要進(jìn)一步確認(rèn)。
最后,任務(wù)池模塊現(xiàn)在屬于基礎(chǔ)版本,接口支持只支持簡單的任務(wù)執(zhí)行和取消功能,查詢運(yùn)行狀態(tài)等接口也不支持。任務(wù)一旦執(zhí)行,不支持取消。不建議您在任務(wù)中執(zhí)行阻塞操作,特別是無限期阻塞操作,長時(shí)間的阻塞操作占據(jù)工作線程,可能會(huì)阻塞其他任務(wù)調(diào)度,影響您的應(yīng)用性能。