# 2.9:异步编程
APP 应用开发过程中,经常使用到网络请求、文件读写以及图片加载等相对比较耗时的操作,如果这些操作都是主线程中执行,就可能造成应用卡顿的情况,为了避免这种情况出现,ArkUI 开发框架提供了 Worker 和 TaskPool 两种方式来支持多线程编程,本节笔者简单介绍一下它们的使用。
# 2.9.1:Worker方式实现
ArkUI 开发框架在 @ohos.worker
模块里提供了对 Worker 线程的支持,Worker 线程是与主线程并行的独立线程,其主要作用是为应用程序提供一个多线程的运行环境来处理耗时任务从而避免阻塞主线程的运行。
创建 Worker 的线程称之为宿主线程,Worker自身的线程称之为 Worker 线程,创建 Worker 传入的 url 文件在 Worker 线程中执行,Worker 线程创建后不会主动销毁,若不处于任务状态一直运行,在一定程度上会造成资源的浪费,应及时关闭空闲的 Worker,Worker 的工作流程简化图如下所示:
编写 Worker 端代码
在 entry 的 ets 目录下创建 worker 目录,然后在 worker 目录下创建
worker.ts
文件,内容如下:// 引入 worker import worker from '@ohos.worker'; let TAG = "WORKER_SUB_THREAD"; // 获取对应的 ThreadWorkerGlobalScope 实例 const workerPort = worker.workerPort; // 给ThreadWorkerGlobalScope的onmessage赋值,监听宿主线程发送过来的消息 workerPort.onmessage = function (data) { console.log(`${TAG}, workerPort.onMessage: ` + JSON.stringify(data)); // 模拟一个耗时任务 setTimeout(() => { // 耗时结束后,向宿主线程发送消息 workerPort.postMessage("Hello, Worker, I'm sub thread"); }, 3000); } // 给ThreadWorkerGlobalScope的onmessageerror赋值,监听异常情况 workerPort.onmessageerror = function (error) { console.log(`${TAG}, workerPort.onmessageerror: ` + JSON.stringify(error)); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23worker.ts
文件里的代码注释的很清晰,先引入worker
,然后获取到 ThreadWorkerGlobalScope 实例对象 workerPort,通过给 workerPort 的 onmessage 和 onmessageerror 属性赋值来监听宿主发送的消息和异常情况,并在 onmessage 的方法内模拟了一个耗时任务,当耗时任务结束后向宿主线程发送消息。给 Worker 添加配置
worker 端代码编写完成后,需要在 entry 目录下的 build-profile.json5 文件的 buildOption 项中添加对 worker 的配置,否则不起作用,配置代码如下:
{ "apiType": 'stageMode', "buildOption": { // buildOption 下添加 "sourceOption": { // sourceOption 下添加 "workers": [ // worker 文件的路径集 "./src/main/ets/worker/worker.ts" // 路径一定要配置正确 ] } } // 省略其它配置项…… }
1
2
3
4
5
6
7
8
9
10
11📢:一定要添加对 worker 的配置。
编写宿主端代码
import worker from '@ohos.worker'; let TAG = "WORKER_MAIN_THREAD"; let URL = "entry/ets/worker/worker.ts"; @Entry @Component struct Index { @State message: string = 'Hello World'; private workerInstance: worker.ThreadWorker; private initWorker() { this.workerInstance = new worker.ThreadWorker(URL, { name: "WORKER_THREAD" }) } private monitorMessage() { this.workerInstance.onmessage = function (data) { console.log(`${TAG}, workerInstance.onMessage: ` + JSON.stringify(data)); } } private sendMessage() { this.workerInstance.postMessage("Hello, Worker, I'm main thread"); } private destroyWorker() { this.workerInstance.terminate(); } build() { Column({space: 8}) { Button("初始化Worker") .height(60) .fontSize(20) .onClick(() => { this.initWorker(); }) Button("监听Worker的消息") .height(60) .fontSize(20) .onClick(() => { this.monitorMessage(); }) Button("给Worker发消息") .height(60) .fontSize(20) .onClick(() => { this.sendMessage(); }) Button("销毁 Worker") .height(60) .fontSize(20) .onClick(() => { this.destroyWorker(); }) } .width('100%') .height('100%') .padding(20) } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67创建 ThreadWorker 的时候需要传递一个 url,该 url 的路径规则是项目在 Ohos 模式下的路径,如下图所示:
依次点击按钮,则打印日志如下所示:
***/JsApp: WORKER_SUB_THREAD, workerPort.onMessage: {"data":"Hello, Worker, I'm main thread"} ***/JsApp: WORKER_MAIN_THREAD, workerInstance.onMessage: {"data":"Hello, Worker, I'm sub thread"}
1
2
📢:Worker 线程一定要在 build-profile.json5 里添加配置,另外 Worker 线程不能操作 UI。宿主线程一旦调用了 ThreadWorker 的 terminate()
方法后不能调用 post 相关方法,否则抛异常:Worker instance is not running, maybe worker is terminated when PostMessage
。
# 2.9.2:TaskPool方式实现
ArkUI 开发框架在 @ohos.taskpool
模块里提供了多线程的运行环境,作用和其它语言里的线程池一样,都是为了降低资源的消耗、提高系统的性能且无需关心线程实例的生命周期,TaskPool 的使用步骤如下:
引入模块
import taskPool from '@ohos.taskpool';
1创建Task
Task 在创建的时候需要接收一个方法和方法运行需要的参数,传递的方法表示其将要在异步线程中执行,参数表示方法运行所需要的参数,如果方法没有参数则不传,样例代码如下所示:
private backgroundTask: taskPool.Task; private createTask() { this.backgroundTask = new taskPool.Task(this.doInBackground, "background params"); } private doInBackground(params: string) { console.log("doInBackground: " + params); }
1
2
3
4
5
6
7
8
9执行Task
Task 创建完毕后,可直接调用 TaskPool 提供的
execute()
方法,代码如下所示:private executeTask() { if (this.backgroundTask) { taskPool.execute(this.backgroundTask); } }
1
2
3
4
5取消Task
TaskPool 提供了
cancel()
方法可允许开发者取消一个未执行的 Task,代码如下所示:private cancelTask() { if (this.backgroundTask) { taskPool.cancel(this.backgroundTask); } }
1
2
3
4
5📢:
cancel()
方法只能取消未执行的 Task,如果 Task 已经执行过或者正在执行则会报错。完整样例
@Entry @Component struct ArkUIClubTaskPoolTest { private backgroundTask: taskPool.Task; build() { Column({space: 10}) { Button("创建Task") .onClick(() => { this.createTask(); }) Button("执行Task") .onClick(() => { this.executeTask(); }) Button("取消Task") .onClick(() => { this.cancelTask(); }) } .width("100%") .height("100%") .padding(10) } // 该方法在子线程中执行 private doInBackground(params: string) { console.log("doInBackground: " + params); } private createTask() { this.backgroundTask = new taskPool.Task(this.doInBackground, "background params"); } private executeTask() { if (this.backgroundTask) { taskPool.execute(this.backgroundTask); } } private cancelTask() { if (this.backgroundTask) { taskPool.cancel(this.backgroundTask); } } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47在开发板上运行代码,依次点击按钮,这控制台打印日志如下所示:
***/JsApp: TASK, task created ***/JsApp: TASK, task will execute ***/JsApp: TASK, doInBackground: background params
1
2
3📢:TaskPool 的
execute()
方法返回值是一个 Promise,所以它是支持异步返回的。
# 2.9.3:小结
本节笔者简单介绍了 ArkUI 开发框架支持的两种异步方式,从代码实现上看使用 TaskPool 是比较简单的,读者可根据自己实际需要选择合适的方式即可。