ArkTs学习之异步并发(Promise),多线程并发(TaskPool,Worker)

一,介绍

并发,有“同”的意思,顾名思义就是允许多个事情,同时发生。并发放在日常生活中很常见,比如我们可以一遍敲着代码,一边喝着饮料,听着音乐;把它放在编程的世界里,就是指的是同一时间内,多段代码同时执行。在ArkTs编程中,并发分为异步并发和多线程并发。这里不要把“异步,同步” 和 “多线程,单线程”放在一个维度里去理解,不然就容易搞的头大,会不由自主的去思考异步和多线程啥区别啥区别?越思考越难以理解,他们只不过在不同场景或者概念下的一种叫法。

二,异步并发

异步并发并不是真正的并发,比如在单核设备中,同时执行多端代码其实是通过CPU快速调度来实现的。举个例子,好比一个程序员,在当前的时间段里,他需要解决一个紧急的bug,但是他又很饿,常规的做法就是它先去吃饭,然后再去解决bug,或者他可以先解决bug,再去吃饭。但是如果两个事都很紧急的情况下,他可能就需要写两行代码,吃一口饭,用这种来回切换的方式去达到同时进行的效果。异步并发一般是放在单线程的概念下去说的,通过把在单线程下,频繁快速切换去达到看似异步并发的目的。如果频繁快速到人视觉上没有感知,那它也算一种并发,只要速度快。

1. Promise

Promise是 JavaScript 中处理异步操作的一种方式,它可以让异步操作更加清晰、易于管理和维护。这里需要说明一下,Promise仅仅是前代程序员前辈,为了对以前单线程下异步操作书写繁琐,代码混乱,不易维护的等弊端想出来的一种优化方式的工具。不要把它和异步操作等同起来了,而且这里仅做简单了解。

🌾 1、基本概念

Promise是一个对象,代表了一个异步操作的最终完成或者失败。它有三种状态:pending(进行中)、fulfilled(已成功)、rejected(已失败)。

一个Promise对象在创建时处于pending状态。当异步操作成功完成时,Promise的状态变为fulfilled,并传递一个值(通常称为 “决议值”)给相应的处理函数。如果异步操作失败,Promise的状态变为rejected,并传递一个错误对象(通常称为 “拒绝理由”)给相应的处理函数。 

🌾 2、主要用途

Promise主要用于处理异步操作,例如网络请求、文件读取、定时器等。它可以避免回调地狱(callback hell)的问题,使代码更加清晰、易于理解和维护。

通过Promise,可以将异步操作以链式调用的方式进行组合和管理,提高代码的可读性和可维护性。

🌾 3、基本用法

🦋 创建 Promise 对象:

使用new Promise()构造函数来创建一个Promise对象。构造函数接受一个函数作为参数,这个函数被称为 “执行器函数”(executor function)。执行器函数接收两个参数,分别是resolvereject函数。

当异步操作成功时,调用resolve函数将Promise的状态变为fulfilled,并传递决议值。当异步操作失败时,调用reject函数将Promise的状态变为rejected,并传递拒绝理由。

🔈:这就像你在美团上把一个任务交给了,一个外卖小哥去处理,同时你给小哥留下了成功,失败的两个沟通渠道。无论成功失败,小哥都会告诉你结果。你就等小哥的电话就行

以下是一个创建Promise对象的示例:

   const promise = new Promise((resolve, reject) => {
     // 模拟异步操作
     setTimeout(() => {
       const randomNumber = Math.random();
       if (randomNumber > 0.5) {
         resolve(randomNumber); //成功了回调
       } else {
         reject(new Error('随机数小于 0.5')); //失败了回调
       }
     }, 1000);
   });

如果就像上面那样,那promise也没啥东西。之所以大家觉得方便,还是promise做了其它优化处理。

🦋 then 方法:

Promise对象的then方法用于指定当Promise状态变为fulfilled时要执行的操作。then方法接受两个参数,第一个参数是一个函数,当Promise状态变为fulfilled时会被调用,并接收决议值作为参数。第二个参数是可选的,是一个函数,当Promise状态变为rejected时会被调用,并接收拒绝理由作为参数。

以下是使用then方法的示例:

   promise.then(
    //成功回调
     (result) => {
       console.log('异步操作成功:', result);
     },
      //失败回调
    (error) => {
       console.log('异步操作失败:', error);
     }
   );

🔈:耗时操作,该在初始化时传给promise外,成功,失败两种状态的回调可以通过.then链式属性函数传入。而且失败的回调你不传入也行

🦋 catch 方法

Promise对象的catch方法用于指定当Promise状态变为rejected时要执行的操作。catch方法接受一个参数,是一个函数,当Promise状态变为rejected时会被调用,并接收拒绝理由作为参数。

以下是使用catch方法的示例:

   promise.catch((error) => {
     console.log('异步操作失败:', error);
   });

🔈:耗时操作,该在初始化时传给promise外,失败这种状态的回调可以通过.catch链式属性函数传入。

🦋 finally 方法

Promise对象的finally方法用于指定无论Promise状态是fulfilled还是rejected,都会执行的操作。finally方法接受一个参数,是一个函数,在Promise状态确定后会被调用。

以下是使用finally方法的示例:

   promise.finally(() => {
     console.log('异步操作完成');
   });
🔈:耗时操作,该在初始化时传给promise外,不管你传不传成功失败回调函数,我都通过.finally属性函数中传递的回调函数告诉你,你安排的任务结束了。
📢 好处:通过Promise封装的耗时操作,我们可以通过.then, .catch,finally 等属性函数链式结构,代码做一个分层隔离,能便于代码的维护和书写。它的其它优化还有很多,可以自己探索

🌾 4、Promise 的链式调用

🦋 原理:

thencatch方法都会返回一个新的Promise对象,这使得可以对Promise进行链式调用。在链式调用中,上一个thencatch方法的返回值会作为下一个thencatch方法的输入。

🔈:如果上一个then方法返回一个非Promise值,下一个then方法会将这个值作为决议值进行处理。如果上一个then方法返回一个Promise对象,下一个then方法会等待这个Promise对象的状态确定后再进行处理。

  /** 生命周期 */
  aboutToAppear(): void {
    const promise1: Promise<string> = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('第一个异步操作完成');
      }, 1000);
    });

    const promise2: Promise<string> = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('第二个异步操作完成');
      }, 1500);
    });

    promise1
      .then((result) => {
        console.log(result); //第一个异步操作完成
        return promise2; //返回一个promise对象
      })
      .then((result) => {
        //第二个异步操作完成
        console.log(result);
      })
      .catch((error: Error) => {
        console.log('异步操作失败:', error);
      })
      .finally(() => {
        console.log('所有异步操作完成');
      });
  }
03-01 02:51:50.475   13771-13771   A03d00/JSAPP                    com.examp...tiondemo  I     第一个异步操作完成
03-01 02:51:50.975   13771-13771   A03d00/JSAPP                    com.examp...tiondemo  I     第二个异步操作完成
03-01 02:51:50.975   13771-13771   A03d00/JSAPP                    com.examp...tiondemo  I     所有异步操作完成

🌾 5、Promise 的并行执行和顺序执行

🦋 并行执行

使用Promise.all()方法可以并行执行多个Promise对象,并在所有Promise对象都变为fulfilled时,返回一个新的Promise对象,其决议值是一个包含所有Promise对象决议值的数组。如果有任何一个Promise对象变为rejected,则返回的Promise对象也会变为rejected,并传递第一个被拒绝的Promise对象的拒绝理由。

以下是使用Promise.all()方法的示例:

   const promise3 = new Promise((resolve, reject) => {
     setTimeout(() => {
       resolve('第三个异步操作完成');
     }, 1200);
   });

   const promise4 = new Promise((resolve, reject) => {
     setTimeout(() => {
       resolve('第四个异步操作完成');
     }, 1800);
   });

   Promise.all([promise1, promise2, promise3, promise4])
    .then((results) => {
       console.log(results);
     })
    .catch((error) => {
       console.log('异步操作失败:', error);
     });
🦋 顺序执行

通过在then方法中返回一个新的Promise对象,可以实现Promise的顺序执行。上一个Promise对象的决议值可以作为下一个Promise对象的输入。

以下是实现Promise顺序执行的示例:

   const step1 = () => {
     return new Promise((resolve, reject) => {
       setTimeout(() => {
         resolve('第一步完成');
       }, 1000);
     });
   };

   const step2 = (result1) => {
     return new Promise((resolve, reject) => {
       setTimeout(() => {
         resolve(result1 + ', 第二步完成');
       }, 1500);
     });
   };

   const step3 = (result2) => {
     return new Promise((resolve, reject) => {
       setTimeout(() => {
         resolve(result2 + ', 第三步完成');
       }, 1200);
     });
   };

   step1()
    .then(step2)
    .then(step3)
    .then((result3) => {
       console.log(result3);
     })
    .catch((error) => {
       console.log('异步操作失败:', error);
     });

🌾 6、Promise 的错误处理

🦋 在 then 和 catch 中处理错误:
🔈:then:然后,接下来;catch:抓住,捕获。

可以在then方法的第二个参数和catch方法中处理Promise被拒绝的情况。这样可以避免错误被忽略,提高代码的健壮性。

以下是在thencatch中处理错误的示例:

   const promise5 = new Promise((resolve, reject) => {
     setTimeout(() => {
       reject(new Error('第五个异步操作失败'));
     }, 1000);
   });

   promise5
    .then((result) => {
       console.log(result);
     })
    .catch((error) => {
       console.log('异步操作失败:', error);
     });
🦋 使用 Promise.all () 时的错误处理:
🔈:all: 全部,所有

当使用Promise.all()方法并行执行多个Promise对象时,如果有任何一个Promise对象被拒绝,Promise.all()返回的Promise对象也会被拒绝。可以在catch方法中处理这个错误。

以下是在Promise.all()中处理错误的示例:

   const promise6 = new Promise((resolve, reject) => {
     setTimeout(() => {
       resolve('第六个异步操作完成');
     }, 1000);
   });

   const promise7 = new Promise((resolve, reject) => {
     setTimeout(() => {
       reject(new Error('第七个异步操作失败'));
     }, 1500);
   });

   Promise.all([promise6, promise7])
    .then((results) => {
       console.log(results);
     })
    .catch((error) => {
       console.log('异步操作失败:', error);
     });

🌾 7、Promise 的高级用法

🦋 Promise.resolve () 和 Promise.reject ():
🔈:resolve:  解决(问题或困难);  reject: 拒绝,否决(提议、建议或请求)。

Promise.resolve()方法返回一个状态为fulfilledPromise对象,并传递一个决议值。

Promise.reject()方法返回一个状态为rejectedPromise对象,并传递一个拒绝理由。

以下是使用Promise.resolve()Promise.reject()的示例:

   const resolvedPromise = Promise.resolve('直接成功');
   const rejectedPromise = Promise.reject(new Error('直接失败'));

   resolvedPromise.then((result) => {
     console.log(result);
   });

   rejectedPromise.catch((error) => {
     console.log('异步操作失败:', error);
   });
🦋 Promise.race():
🔈:race 赛跑,速度竞赛;竞争,角逐;

Promise.race()方法接受一个Promise对象数组作为参数,并返回一个新的Promise对象。这个新的Promise对象的状态会随着数组中第一个状态改变的Promise对象的状态而改变,并传递相应的决议值或拒绝理由。

以下是使用Promise.race()的示例:

   const promise8 = new Promise((resolve, reject) => {
     setTimeout(() => {
       resolve('第八个异步操作完成(慢)');
     }, 2000);
   });

   const promise9 = new Promise((resolve, reject) => {
     setTimeout(() => {
       resolve('第九个异步操作完成(快)');
     }, 1500);
   });

   Promise.race([promise8, promise9])
    .then((result) => {
       console.log(result);
     })
    .catch((error) => {
       console.log('异步操作失败:', error);
     });

Promise是 JavaScript 中处理异步操作的一种强大方式,它可以使异步代码更加清晰、易于管理和维护。通过掌握Promise的基本用法、链式调用、并行执行和顺序执行、错误处理以及高级用法,可以更好地处理异步操作,提高代码的质量和可维护性。

2. async/await

Promise对象的.then回调函数在一些情况下会产生回调函数地狱情况,代码嵌套一层套一层,可读性变差,很容易变成“屎山代码”,在这种情况下先是产生了链式编程的方式避免嵌套,可是依然曾在回调函数,可读性还是不高。async/await作为Promise语法糖解决了这个问题。

🌰 举个例子

🍀 1、定义一个返回值类型为Promise对象的函数
function showCount(count: number): Promise<string> {
  //返回一个promise对象
  return new Promise<string>((resolve) => {
    // 异步方法setTimeout
    setTimeout(() => {
      resolve(count.toString())
    }, 1000)
  })
}
🍀 2、回调地狱写法:
🔈:代码嵌套一层套一层,可读性变差,很容易变成“屎山代码”
function fn {
  // 2. 第一层调用函数
  showCount(1)
    .then((num) => {
      // 第1秒后出现num=1
      console.log('num -----> ', num)
      // 3. 第二层调用函数
      showCount(2)
        .then(() => {
          // 第2秒后出现num=2
          console.log('num -----> ', num)
          // 4. 第三层调用函数
          showCount(3)
            .then((num) => {
              // 第3秒后出现num=3
              console.log('num -----> ', num)
              // 5. 第五层调用函数
              showCount(4)
                .then((num) => {
                  // 第4秒后出现num=4
                  console.log('num -----> ', num)
                })
            })
        })
    })
}
🍀 3、链式编程优化写法
🔈:通过链式编程,可以把嵌套从回调函数里面解放出来
在.then回调函数中return一个Promise对象就可以使用链式写法
function fn {
  // 2. 第一层调用函数
  showCount(1)
    .then((num) => {
      // 第1秒后出现num=1
      console.log('num -----> ', num)
      // 3. 第二层调用函数
      return showCount(2)
    })
    .then((num) => {
      // 第2秒后出现num=2
      console.log('num -----> ', num)
      // 4. 第三层调用函数
      return showCount(3)
    })
    .then((num) => {
      // 第3秒后出现num=3
      console.log('num -----> ', num)
      // 5. 第五层调用函数
      return showCount(4)
    })
    .then((num) => {
      // 第4秒后出现num=4
      console.log('num -----> ', num)
    })
}
🍀 4、async/await 语法糖优化:
🔈:虽然通过promise链式编程,可以把嵌套从回调函数里面解放出来,但是没事存在代码臃肿的问题。所以前辈程序员又在promise基础上,定义了async/await。  async/await 可以通过同步的方式执行异步任务。
// 1. 定义一个返回值类型为Promise对象的async函数
async function showCount2(count: number): Promise<string> {
  return await new Promise<string>((resolve) => {
    // 异步方法setTimeout
    setTimeout(() => {
      resolve(count.toString())
    }, 1000)
  })
}
async function fn() {
  // 2. 第一层调用函数
  const num1 = await showCount(1)
  // 第1秒后出现num=1
  console.log('num1 -----> ', num1)
  // 3. 第二层调用函数
  const num2 = await showCount(2)
  // 第2秒后出现num=2
  console.log('num2 -----> ', num2)
  // 4. 第三层调用函数
  const num3 = await showCount(3)
  // 第3秒后出现num=3
  console.log('num3 -----> ', num3)
  // 5. 第五层调用函数
  const num4 = await showCount(4)
  // 第4秒后出现num=4
  console.log('num4 -----> ', num4)
}

🌾 1、理解作用

没学过的朋友可能会问:“同步方式执行异步任务体现在哪里呢?”。我们用一个来对比说明一下。

现在我们要实现一个红绿灯的效果,通过异步任务依次输出 绿、黄、红 (时间的模拟暂时不考虑) 如果用传统的 Promise 实现:

Promise.resolve('绿').then((res) => {
  console.log(res)

  Promise.resolve('').then((res) => {
    console.log(res)

    Promise.resolve('').then((res) => {
      console.log(res)
    })
  })
}) 

我们可以用 async/await 来实现一下:

// 立即执行函数
(async () => {
  await Promise.resolve('绿').then(res => console.log(res))
  await Promise.resolve('').then(res => console.log(res))
  await Promise.resolve('').then(res => console.log(res))
})() 

通过前后对比,我们可以发现,在需要异步任务按照顺序严格执行的情况下, async/await 可以避免嵌套过多的情况,取而代之的是简单易懂的同步形式代码。

🌾 2、语法简述

既然大家了解了 async/await 的作用,那么接下来我们去了解一下他的语法规则:

🦋 Async 修饰符

通过在函数声明前加上async关键字,可以将任何函数转换为返回Promise的异步函数。这意味着你可以使用.then().catch()来处理它们的结果。

//1. 加了async修饰的函数
async function showCount(count: number) {
  setTimeout(() => {
    count.toString()
  }, 1000)
}

相当于

//普通函数
function showCount1(count: number): Promise<string> {
  //返回一个promise对象
  return new Promise<string>((resolve) => {
    // 异步方法setTimeout
    setTimeout(() => {
      resolve(count.toString())
    }, 1000)
  })
}

所以

//可以通过then和catch链式语法处理
showCount(3).then((value)=>console.log(value))
🦋 await 修饰符

await关键字只能在async函数内部使用。它可以暂停async函数的执行,等待Promise的解决(resolve),获取的是Promise函数中resolve或者reject的值,然后以Promise的值继续执行函数。

🔈:如果await 后面并不是一个Promise的返回值,则会按照同步程序返回值处理
async function asyncFunction() {
  let promise = new Promise((resolve, reject) => {
    setTimeout(() => resolve("完成"), 1000)
  });
  let result = await promise; // 等待,直到promise解决 (resolve)
  console.log(result); // "完成"
}

asyncFunction();
🦋 错误处理

async/await中,错误处理可以通过传统的try...catch语句实现,这使得异步代码的错误处理更加直观。

async function asyncFunction() {
  try {
    let response = await fetch('http://example.com');
    let data = await response.json();
    // 处理数据
  } catch (error) {
    console.log('捕获到错误:', error);
  }
}

asyncFunction();

🌾 3、异步任务处理的场景

理解了 async/await 的原理,我们还需要熟练面对各种异步任务处理的场景,接下来我会例举一些涉及到异步任务的业务场景,来分享一些我对异步任务的处理思路。

🦋 用户登录后获取用户数据

业务逻辑:

  • 调用用户登录接口,返回 token
  • 调用获取用户信息接口,携带 token,返回 userInfo

这里的逻辑很简单,存在一个排队的情况,用 async/await 实现再适合不过了

const login = async () => {
  const loginRes = await loginAPI()
  // loginAPI接口返回的token会存入localStorage,作为默认参数传递
  // 因此getUSerInfo不用手动传token
  const userInfoRes = await getUserInfo()
}
🦋 图片上传

业务逻辑:

  • 通过 FormData 格式传输图片,返回图片 url
  • 在某表单提交的接口上传表单数据,携带 url

跟上个情景类似,但这边还存在批量图片上传的情况,这里可以使用 Promise.all,这里解释一下这样使用的优缺点:

  • 优点:不用排队,图片批量上传速度快
  • 缺点:任何一个请求失败,返回 reject(ps:可以用 Promise.allSettled 解决问题)
const filePromsie = (file) => {
  const formdata = new FormData()
  formdata.append(file)
  return fileSubmitAPI(formdata)
}

const submit = (files) => {
  const promiseArr = []
  files.length && files.forEach((file) => {
    promiseArr.push(filePromsie(file))
  })
  Promise.all(promiseArr).then((res) => {
    tableSubmitAPI({ res, ...tableData })
  })
}

三,多线程并发

上面我们说promise 和 async、await 是在单线程下,实现异步并发的一种方式,但这种并发毕竟是通过靠cpu快速调度来实现任务的同步执行。现在介绍一下多线程并发的方式。

1. 概述

并发模型是用来实现不同应用场景中并发任务的编程模型,常见的并发模型分为基于内存共享的并发模型和基于消息通信的并发模型

🦋 内存共享的并发模型:线程间共享内存,内存的操作有排他性。

内存共享并发模型中,多个线程同时执行复数任务,这些线程依赖同一内存并且都有权限访问,线程访问内存前需要抢占并锁定内存的使用权,没有抢占到内存的线程需要等待其他线程释放使用权再执行。

🦋 消息通信的并发模型:Actor模型不同角色之间并不共享内存,生产者线程和UI线程都有自己独占的内存。

Actor并发模型中,每一个线程都是一个独立Actor,每个Actor有自己独立的内存,Actor之间通过消息传递机制触发对方Actor的行为,不同Actor之间不能直接访问对方的内存空间。

Actor模型不同角色之间并不共享内存,生产者线程和UI线程都有自己的虚拟机实例,两个虚拟机实例之间拥有独占的内存,相互隔离。生产者生产出结果后通过序列化通信将结果发送给UI线程,UI线程消费结果后再发送新的生产任务给生产者线程。

Actor并发模型作为基于消息通信并发模型的典型代表,不需要开发者去面对锁带来的一系列复杂偶发的问题,同时并发度也相对较高,因此得到了广泛的支持和使用。

🔈:当前ArkTS提供了TaskPool和Worker两种并发能力,TaskPool和Worker都基于Actor并发模型实现。

2. TaskPool

🌾 1、基本介绍

TaskPool 作用是为应用程序提供一个多线程的运行环境,降低整体资源的消耗、提高系统的整体性能,且您无需关心线程实例的生命周期。

有一定开发经验的朋友应该对线程池不陌生。大致描述如下:

1)TaskPool允许开发者在主线程封装任务抛给任务队列,系统选择合适的工作线程,进行任务的分发及执行,再将结果返回给主线程。
2)接口直观易用,支持任务的执行、取消,以及指定优先级的能力,同时通过系统统一线程管理,结合动态调度及负载均衡算法,可以节约系统资源。
3)系统默认会启动一个任务工作线程,当任务较多时会扩容,工作线程数量上限跟当前设备的物理核数相关,具体数量内部管理,保证最优的调度及执行效率,长时间没有任务分发时会缩容,减少工作线程数量。

示意图如下:

 

🌾 2、TaskPool接口介绍

🦋 模块导入
import { taskpool } from '@kit.ArkTS';
🦋 核心API

1. 将待执行的函数放入taskpool内部任务队列 (函数不会立即执行,而是等待分发到工作线程执行。当前执行模式不可取消任务)

execute(func: Function, ...args: Object[]): Promise<Object>

2. 将创建好的任务放入taskpool内部任务队列 (任务不会立即执行,而是等待分发到工作线程执行。当前执行模式可以设置任务优先级和尝试调用cancel进行任务取消。该任务不可以是任务组任务和串行)

execute(task: Task, priority?: Priority): Promise<Object>

3. 延时执行任务。当前执行模式可以设置任务优先级和尝试调用cancel进行任务取消。该任务不可以是任务组任务、串行队列任务和周期任务。若该任务非长时任务,可以多次调用executeDelayed执行,长时任务仅支持执行一次。

executeDelayed(delayTime: number, task: Task, priority?: Priority): Promise<Object>

4. 周期执行任务,每隔period时长执行一次任务。当前执行模式支持设置任务优先级和调用cancel取消任务周期执行。周期任务不可以是任务组任务和串行队列任务,不可以再次调用执行接口,不可以拥有依赖关系。

executePeriodically(period: number, task: Task, priority?: Priority): void

5. 取消任务池中的任务。当任务在taskpool等待队列中,取消该任务后该任务将不再执行,并返回undefined作为结果;当任务已经在taskpool工作线程执行,取消该任务并不影响任务继续执行,执行结果在catch分支返回,搭配isCanceled使用可以对任务取消行为作出响应。taskpool.cancel对其之前的taskpool.execute/taskpool.executeDelayed生效。

cancel(task: Task): void

6. 取消任务池中的任务组。当一个任务组的任务未全部执行结束时取消任务组,返回undefined作为任务组结果。

cancel(group: TaskGroup): void

7. 中止任务池中的长时任务,在长时任务执行完成后调用。中止后,执行长时任务的线程可能会被回收。

terminateTask(longTask: LongTask): void

8. 检查函数是否为并发函数。

isConcurrent(func: Function): boolean

9. 获取任务池内部信息,包含线程信息和任务信息。

getTaskPoolInfo(): TaskPoolInfo

10. 在核心API中,有几个关键的类或枚举:Priority、Task。

定义如下:​​​​​​​

🍀 Priority 枚举
enum Priority {
  HIGH = 0, //任务为高优先级。
  MEDIUM = 1, // 任务为中优先级。
  LOW = 2, // 任务为低优先级。
  IDLE = 3 //任务为后台任务。
}
🍀 Task 类
class Task {
  // 属性
  function: Function // 创建任务时需要传入的函数,支持的函数返回值类型请查序列化支持类型。
  arguments: Object[] // 创建任务传入函数所需的参数,支持的参数类型请查序列化支持类型。
  name: string // 创建任务时指定的任务名称。
  totalDuration: number // 执行任务总耗时。
  ioDuration: number // 执行任务异步IO耗时。
  cpuDuration: number // 执行任务CPU耗时。

  // 方法
  // 构造函数
  constructor(func: Function, ...args: Object[])
  constructor(name: string, func: Function, ...args: Object[])
  
  static isCanceled(): boolean; // 任务是否已经取消

  isDone(): boolean // 检查任务是否已完成。

  /**
   设置任务的传输列表。
   此接口可以设置任务池中ArrayBuffer的transfer列表,transfer列表中的ArrayBuffer对象在传输时不会复制buffer内容到工作线程而是转移buffer控制权至工作线程,传输后当前的ArrayBuffer失效。若ArrayBuffer为空,则不会transfer转移。
   */
  setTransferList(transfer?: ArrayBuffer[]): void

  /**
   设置任务的拷贝列表。
   需搭配@Sendable装饰器使用,否则会抛异常。
   */
  setCloneList(cloneList: Object[] | ArrayBuffer[]): void

  // 在任务执行过程中向宿主线程发送消息并触发回调。使用该方法前需要先构造Task。
  static sendData(...args: Object[]): void

  // 为任务注册回调函数,以接收和处理来自任务池工作线程的数据。使用该方法前需要先构造Task。
  onReceiveData(callback?: Function): void

  // 为当前任务添加对其他任务的依赖。使用该方法前需要先构造Task。该任务和被依赖的任务不可以是任务组任务、串行队列任务、已执行的任务和周期任务。存在依赖关系的任务(依赖其他任务的任务或被依赖的任务)执行后不可以再次执行。
  addDependency(...tasks: Task[]): void

  // 删除当前任务对其他任务的依赖。使用该方法前需要先构造Task。
  removeDependency(...tasks: Task[]): void

  // 注册一个回调函数,并在任务入队时调用它。需在任务执行前注册,否则会抛异常。
  onEnqueued(callback: CallbackFunction): void

  // 注册一个回调函数,并在执行任务前调用它。需在任务执行前注册,否则会抛异常。
  onStartExecution(callback: CallbackFunction): void

  // 注册一个回调函数,并在任务执行失败时调用它。需在任务执行前注册,否则会抛异常。
  onExecutionFailed(callback: CallbackFunctionWithError): void

  // 注册一个回调函数,并在任务执行成功时调用它。需在任务执行前注册,否则会抛异常。
  onExecutionSucceeded(callback: CallbackFunction): void
}

🌾 3、taskpool 使用

 

🦋 延时任务(executeDelayed)

官方API:

// delayTime: 延时时间 单位毫秒
// task: 需要延时执行的任务
// priority: 延时执行任务的优先级
executeDelayed(delayTime: number, task: Task, priority?: Priority): Promise<Object>

无参数、无返回值延时任务

import { taskpool } from '@kit.ArkTS'

// 延时任务函数, 使用@Concurrent修饰的函数
@Concurrent
function delayedFunc(): void {
  console.log('delayedFunc =================被执行')
}

@Entry
@Component
struct Index {
  /** 构建函数 */
  build() {
    Button('延迟任务').onClick((event: ClickEvent) => {
      console.log('START====================' + Date.now().toString())
      //1. 首先我们需要创建一个延时任务
      //Task表示任务, 必须要使用concurrent方法来构造Task, 也就是使用@Concurrent修饰的函数
      const delayedTask = new taskpool.Task(delayedFunc)
      //2.执行这个延迟任务
      taskpool.executeDelayed(2000, delayedTask, taskpool.Priority.HIGH).then((res) => {
        console.log('END====================' + Date.now().toString())
      })
    })
  }
}

输出结果:

有参数、有返回值的延时任务

import { taskpool } from '@kit.ArkTS'

// 延时任务函数, 使用@Concurrent修饰的函数
@Concurrent
function delayedFunc(a: number, b: number): number {
  console.log('delayedFunc =================被执行')
  return a + b
}

@Entry
@Component
struct Index {
  /** 构建函数 */
  build() {
    Button('延迟任务').onClick((event: ClickEvent) => {
      console.log('START====================' + Date.now().toString())
      //1. 首先我们需要创建一个延时任务
      //Task表示任务, 必须要使用concurrent方法来构造Task, 也就是使用@Concurrent修饰的函数
      const delayedTask = new taskpool.Task(delayedFunc, 100, 200)
      //2.执行这个延迟任务
      // 其实除了通过.then方法获取返回值时, Arkts还提供了另外一种方式:onReceiveData, 注册回调的方式来接受值,需要再任务执行函数中调用taskpool.Task.sendData(xxx); 可以参考官方文档。
      taskpool.executeDelayed(2000, delayedTask, taskpool.Priority.HIGH).then((res) => {
        // res是task任务执行后的结果
        console.log("res=======================" + JSON.stringify(res))
        console.log('END====================' + Date.now().toString())
      })
    })
  }
}

 

输出结果:

🔈 注意: 在延时任务还没有调用执行的时候, 我们可以通过调用taskpool.cancel(delayedTask )来取消该延时任务。

🦋 优先级任务(Prioity)
import { taskpool } from '@kit.ArkTS'

// 执行任务函数
@Concurrent
function priorityFunc(value: string) {
  console.log(`======================${value}:任务被执行`)
}

@Entry
@Component
struct Index {
  /** 成员变量 */
  private taskArray: taskpool.Task[] = []

  /** 构建函数 */
  build() {
    Button('优先级任务').onClick((event: ClickEvent) => {
      // 创建一个优先级的任务
      for (let i = 0; i < 9; i += 3) {
        this.taskArray.push(new taskpool.Task(priorityFunc, `高优先级${i}`));
        this.taskArray.push(new taskpool.Task(priorityFunc, `中优先级${i + 1}`));
        this.taskArray.push(new taskpool.Task(priorityFunc, `低优先级${i + 2}`));
      }

      //执行所有的任务
      for (let i = 0; i < this.taskArray.length; i += 3) {
        taskpool.execute(this.taskArray[i], taskpool.Priority.HIGH).then(async (res: object) => {

        });
        taskpool.execute(this.taskArray[i+1], taskpool.Priority.MEDIUM).then(async (res: object) => {

        });
        taskpool.execute(this.taskArray[i+2], taskpool.Priority.LOW).then(async (res: object) => {

        });
      }
    })
  }
}

输出结果:

 

🦋 依赖任务(Dependent)
import { taskpool } from '@kit.ArkTS'


// task任务执行函数
@Concurrent
function dependentFunc(value: number) {
  console.log(`======================${value}:任务被执行`)
}

@Entry
@Component
struct Index {
  /** 成员变量 */
  private taskArray: taskpool.Task[] = []

  /** 构建函数 */
  build() {
    Button('依赖任务示例').onClick((event: ClickEvent) => {
      //1. 创建依赖任务
      for (let i = 0; i < 5; i++) {
        const task = new taskpool.Task(dependentFunc, i)
        this.taskArray.push(task)
        if (i > 0) {
          // 创建任务的依赖关系,本次任务依赖于上一个任务的完成
          task.addDependency(this.taskArray[i - 1])
        }
      }

      //2. 执行任务
      for (let index = 0; index < this.taskArray.length; index++) {
        const element = this.taskArray[index];
        taskpool.execute(element, taskpool.Priority.HIGH).then((res) => {

        })
      }
    })
  }
}

输出结果:

🦋 任务组(TaskGroup)
import { taskpool } from '@kit.ArkTS'


// 任务组的执行函数
@Concurrent
function taskGroupFunc(value: number) {
  taskpool.Task.sendData('通知结果回调===sendData================' + value)
}

@Entry
@Component
struct Index {
  /** 成员变量 */
  private taskArray: taskpool.Task[] = []
  // 创建一个任务组
  private taskGroup: taskpool.TaskGroup = new taskpool.TaskGroup();

  /** 构建函数 */
  build() {
    Button('任务组示例').onClick((event: ClickEvent) => {
      for (let i = 0; i < 10; i++) {
        this.taskArray.push(new taskpool.Task(taskGroupFunc, i));
        this.taskArray[i].onReceiveData((value: string) => {
          console.log('value=====================' + value)
        })
        // 把task任务添加到TaskGroup任务组中去
        this.taskGroup.addTask(this.taskArray[i]);
      }

      // 执行任务组
      taskpool.execute(this.taskGroup).then((res) => {
        console.log("taskGroup 被执行完毕======================")
      })
    })
  }
}

输出结果:

🦋 串行队列(sequenceRunner)

 

import { taskpool } from '@kit.ArkTS'

//任务执行函数
@Concurrent
function sequenceRunnerFunc(value: number) {
  taskpool.Task.sendData('sequenceRunnerFunc===================' + value)
}

@Entry
@Component
struct Index {
  /** 成员变量 */
  private taskArray: taskpool.Task[] = []
  private runner: taskpool.SequenceRunner = new taskpool.SequenceRunner()

  /** 构建函数 */
  build() {
    Button('串行队列示例').onClick((event: ClickEvent) => {
      // 创建任务
      for (let i = 0; i < 10; i++) {
        this.taskArray.push(new taskpool.Task(sequenceRunnerFunc, i));
        this.taskArray[i].onReceiveData((value: string) => {
          console.log('value=====================' + value)
        })
      }

      // 执行任务
      for (let i = 0; i < this.taskArray.length; i++) {
        this.runner.execute(this.taskArray[i]).then(async () => {
          if (i === this.taskArray.length - 1) {
            console.log("串行线程的任务全部被执行完毕======================")
          }
        });
      }
    })
  }
}

输出函数:

🦋 长线程任务(LongTask)

表示长时任务。LongTask继承自Task。

长时任务不设置执行时间上限,长时间运行不会触发超时异常,但不支持在任务组(TaskGroup)执行和多次执行。

执行长时任务的线程一直存在,知道执行完后调用terminateTask, 该线程会在空闲时间回收

示例:

import { taskpool } from '@kit.ArkTS'

// 长任务执行方法
@Concurrent
function longFunc(time: number) {
  taskpool.Task.sendData('LongTask message===================START')

  // 线程任务可以执行长时间任务
  let start = new Date().getTime();
  while (new Date().getTime() - start < time) {
    continue;
  }
  taskpool.Task.sendData('LongTask message===================END')
}


@Entry
@Component
struct Index {

  /** 构建函数 */
  build() {
    Button('长线程任务示例').onClick((event: ClickEvent) => {
      let longTask = new taskpool.LongTask(longFunc, 10000)
      longTask.onReceiveData((value: string) => {
        console.log("onReceiveData====================" + value)
        taskpool.terminateTask(longTask)
      })

      // 执行长任务
      taskpool.execute(longTask, taskpool.Priority.HIGH).then((res) => {
        console.log("execute===================" + JSON.stringify(res))
      })
    })
  }
}

输出结果:

🦋 周期任务
executePeriodically(period: number, task: Task, priority?: Priority): void

周期执行任务,每隔period时长执行一次任务。当前执行模式支持设置任务优先级和调用cancel取消任务周期执行。周期任务不可以是任务组任务和串行队列任务,不可以再次调用执行接口,不可以拥有依赖关系

示例

import { taskpool } from '@kit.ArkTS'

// 周期任务执行函数
@Concurrent
function periodicFunc() {
  taskpool.Task.sendData("periodic周期任务")
}

@Entry
@Component
struct Index {
  /** 构建函数 */
  build() {
    Button('周期任务').onClick((event: ClickEvent) => {
      console.log("START=================" + Date.now().toString())
      //创建一个任务
      let task = new taskpool.Task(periodicFunc)
      task.onReceiveData((value: string) => {
        console.log("onReceiveData=================" + value + '====' + Date.now().toString())
      })
      taskpool.executePeriodically(2000, task, taskpool.Priority.HIGH)
    })
  }
}

输出结果:

🔈 注意: 该执行函数 调用之后, 不是立刻执行哈, 需要等待设置的period值后,才开始执行。

3. Worker

🌾 1、基本介绍

Worker主要作用是为应用程序提供一个多线程的运行环境,可满足应用程序在执行过程中与宿主线程分离,在后台线程中运行一个脚本进行耗时操作,极大避免类似于计算密集型或高延迟的任务阻塞宿主线程的运行。

🌾 2、Worker的注意事项

Worker创建后需要手动管理生命周期,且最多同时运行的Worker子线程数量为64个。

Worker的创建和销毁耗费性能,建议开发者合理管理已创建的Worker并重复使用。Worker空闲时也会一直运行,因此当不需要Worker时,可以调用terminate()接口或close()方法主动销毁Worker。若Worker处于已销毁或正在销毁等非运行状态时,调用其功能接口,会抛出相应的错误。

Worker的数量由内存管理策略决定,设定的内存阈值为1.5GB和设备物理内存的60%中的较小者。在内存允许的情况下,系统最多可以同时运行64个Worker。如果尝试创建的Worker数量超出这一上限,系统将抛出错误:“Worker initialization failure, the number of workers exceeds the maximum.”。实际运行的Worker数量会根据当前内存使用情况动态调整。一旦所有Worker和主线程的累积内存占用超过了设定的阈值,系统将触发内存溢出(OOM)错误,导致应用程序崩溃。

由于不同线程中上下文对象是不同的,因此Worker线程只能使用线程安全的库,例如UI相关的非线程安全库不能使用。

序列化传输的数据量大小限制为16MB

使用Worker模块时,需要在宿主线程中注册onerror接口,否则当Worker线程出现异常时会发生jscrash问题。

不支持跨HAP使用Worker线程文件

不支持在Worker工作线程中使用AppStorage。

🌾 3、创建Worker的注意事项

Worker线程文件需要放在"{moduleName}/src/main/ets/"目录层级之下,否则不会被打包到应用中。有手动和自动两种创建Worker线程目录及文件的方式。

🦋 手动创建Worker线程

开发者需要手动创建相关目录及文件, 此时需要配置build-profile.json5的相关字段信息,Worker线程文件才能确保被打包到应用中

在当前使用Worker线程的模块下面的对应文件目录中创建一个worker线程文件, 并增加相关配置项

手动创建Worker需要实现代码:

import worker, { ErrorEvent, MessageEvents, ThreadWorkerGlobalScope } from '@ohos.worker'
import { JSON } from '@kit.ArkTS'

// 创建woker线程中和宿主线程通信的对象
const workerPort: ThreadWorkerGlobalScope = worker.workerPort

//woker线程接受主线程的消息
workerPort.onmessage = (e: MessageEvents) => {
  // 现成通信的具体逻辑
  console.log("onmessage=22222================" + JSON.stringify(e.data))

  // woker线程像宿主线程发送消息
  workerPort.postMessage("woker线程发送消息")
}

// 回调函数。表示当Worker对象接收到一条无法被序列化的消息时被调用的事件处理程序,
// 处理程序在宿主线程中执行。其中回调函数中event类型为MessageEvents,表示收到的Worker消息数据。
workerPort.onmessageerror = (ev: MessageEvents) => {
  console.log('onmessageerror=================' + JSON.stringify(ev.data))
}

// worker线程发生error错误的回调
workerPort.onerror = (err: ErrorEvent) => {
  console.log("worker.ets onerror" + err.message);
}
🦋 自动创建Worker现成

DevEco Studio支持一键生成Worker,在对应的{moduleName}目录下任意位置,点击鼠标右键 > New > Worker,即可自动生成Worker的模板文件及配置信息,无需再手动在build-profile.json5中进行相关配置。

当我们使用工具进行自动创建时, 我们可以看到配置文件中自动增加了,工具创建的AutoWorker线程类:

使用自动创建的方式,系统自动实现的代码:

import { ErrorEvent, MessageEvents, ThreadWorkerGlobalScope, worker } from '@kit.ArkTS';

const workerPort: ThreadWorkerGlobalScope = worker.workerPort;

/**
 * Defines the event handler to be called when the worker thread receives a message sent by the host thread.
 * The event handler is executed in the worker thread.
 *
 * @param e message data
 */
workerPort.onmessage = (e: MessageEvents) => {
}

/**
 * Defines the event handler to be called when the worker receives a message that cannot be deserialized.
 * The event handler is executed in the worker thread.
 *
 * @param e message data
 */
workerPort.onmessageerror = (e: MessageEvents) => {
}

/**
 * Defines the event handler to be called when an exception occurs during worker execution.
 * The event handler is executed in the worker thread.
 *
 * @param e error message
 */
workerPort.onerror = (e: ErrorEvent) => {
}

🌾 4. 跨har包加载Worker

跨har的worker线程:

在entry模块中使用workerhar模块中创建的worker线程:

//宿主线程中创建woker线程
const workerInstance = new worker.ThreadWorker("@workerhar/ets/workers/Worker.ets")

//宿主线程像woker线程发送消息
workerInstance.postMessage("宿主线程像夸har包的woker线程传递消息========")

//宿主线程接受woker线程信息
workerInstance.onmessage = (e: MessageEvents) => {
  console.log('onmessage=接受到夸har包的worker线程消息====================' + JSON.stringify(e.data))
  //销毁Worker对象
  workerInstance.terminate()
}

// 在调用terminate后,执行onexit
workerInstance.onexit = (code) => {
  console.log("main thread terminate");
}

workerInstance.onerror = (err: ErrorEvent) => {
  console.log("main error message " + err.message);
}

输出结果:

🔈 注意: 主要区别是在与worker文件的导入路径

🌾 5. 多级Worker的声明周期管理

由于支持创建多级Worker(即通过父Worker创建子Worker的机制形成层级线程关系),且Worker线程生命周期由用户自行管理,因此需要注意多级Worker生命周期的正确管理。若用户销毁父Worker时未能结束其子Worker的运行,会产生不可预期的结果。建议用户确保子Worker的生命周期始终在父Worker生命周期范围内,并在销毁父Worker前先销毁所有子Worker。

示例:

🦋 主线程核心代码:
// 在主线程创建worker线程(父worker),在worker线程中在次创建worker线程(子线程)
const parentworker = new worker.ThreadWorker('../workers//ParentWorker')

parentworker.onmessage = (e: MessageEvents) => {
  console.log('主线程收到父worker线程信息========================' + e.data)
}

parentworker.onexit = () => {
  console.log("父worker退出=============================")
}

parentworker.onerror = (error: ErrorEvent) => {
  console.log('主线程收到父worker的报错=======================' + error)
}

parentworker.postMessage('主线程发送消息给父worker')
})
🦋 parentworker.ets的核心代码:
workerPort.onmessage = (e: MessageEvents) => {
  if (e.data === "主线程发送消息给父worker") {
    // 创建一个子worker
    let childworker = new worker.ThreadWorker("../workers/ChilderWorker");

    childworker.onmessage = (e: MessageEvents) => {
      console.log("父Worker收到子Worker的信息================" + e.data);

      if (e.data === '子Worker向父Worker发送信息') {
        workerPort.postMessage("父Worker向主线程发送信息");
      }
    }

    childworker.onexit = () => {
      console.log('子Worker退出=================')
      workerPort.close()
    }

    childworker.onerror = (err: ErrorEvent) => {
      console.log("子Worker发生报错 " + err);
    }

    childworker.postMessage("父Worker向子Worker发送信息");
  }
}
🦋 childerworker.ets核心代码:

 

workerPort.onmessage = (e: MessageEvents) => {
  if (e.data === "主线程发送消息给父worker") {
    // 创建一个子worker
    let childworker = new worker.ThreadWorker("../workers/ChilderWorker");

    childworker.onmessage = (e: MessageEvents) => {
      console.log("父Worker收到子Worker的信息================" + e.data);

      if (e.data === '子Worker向父Worker发送信息') {
        workerPort.postMessage("父Worker向主线程发送信息");
      }
    }

    childworker.onexit = () => {
      console.log('子Worker退出=================')
      workerPort.close()
    }

    childworker.onerror = (err: ErrorEvent) => {
      console.log("子Worker发生报错 " + err);
    }

    childworker.postMessage("父Worker向子Worker发送信息");
  }
}

🌾 6. Worker和宿主线程的通信

worker的代码是上面的手动创建worker示例的代码.

示例代码:

//宿主线程中创建woker线程
const workerInstance = new worker.ThreadWorker("../manualcreate/manualWorker")

//宿主线程像woker线程发送消息
workerInstance.postMessage("宿主线程像woker线程传递消息========")

//宿主线程接受woker线程信息
workerInstance.onmessage = (e: MessageEvents) => {
  console.log('onmessage=1111====================' + JSON.stringify(e.data))


  //销毁Worker对象
  workerInstance.terminate()
}

// 在调用terminate后,执行onexit
workerInstance.onexit = (code) => {
  console.log("main thread terminate");
}

workerInstance.onerror = (err: ErrorEvent) => {
  console.log("main error message " + err.message);
}

输出结果:

 

posted on 2025-03-01 19:47  梁飞宇  阅读(267)  评论(0)    收藏  举报