# Worker

现代CPU都是多核的。主线程的代码是运行在CPU的主核上的。可以通过 worker api来利用其他核并行计算,加快运算速度。

uni-app x的代码,默认都是在主线程执行的,主线程也称为UI线程。

当需要使用子线程能力时,可以通过本API操作。

当然本API只是一种跨端封装,并且为了跨端还约束了一些写法。开发者也可以在uts插件中自行使用纯原生代码来操作线程。

注意:

  • 所有UI操作、界面绘制,都必须在主线程进行。也就是子线程不能操作DOM API、不能操作绑定在界面上的响应式变量。
  • Android上通过surface渲染的界面组件,可以在子线程操作。
  • 线程之间通信,可以post消息,也可以共享变量。web和小程序仅支持shareArrayBuffer数据类型的共享。App平台没有限制,引用类型都可以共享变量。但共享变量时,需要开发者注意线程安全问题,避免多线程同时写同一个变量,或一个线程读、同时另一个线程在写同一个变量,这可能引发崩溃。
  • 线程和Android的协程是不同的。Android上request api内部已经使用了协程。
  • CPU的核数有限,不要同时开太多线程。

常见场景:

  • 当你的界面掉帧时,应该检查是什么耗时任务导致不能及时渲染,是否可以剥离一些计算任务到子线程来做。
  • 多份大数据需要尽快处理,比如多个json文件需要压缩,可以启动多线程。

# uni.createWorker(url)

创建一个Worker对象

# createWorker 兼容性

Web 微信小程序 Android Android uni-app x UTS 插件 iOS iOS uni-app x UTS 插件 HarmonyOS HarmonyOS uni-app x UTS 插件
4.81 4.41 4.81 4.81 x 4.81 4.81 4.81

# 参数

名称 类型 必填 默认值 兼容性 描述
url string -
Worker脚本的URL

# 返回值

类型
Worker
名称 类型 必备 默认值 兼容性 描述
env WorkerEnv -
worker内的环境变量
名称 类型 必备 默认值 兼容性 描述
USER_DATA_PATH string -
文件系统中的用户目录路径 (本地路径)

# Worker 的方法

# onMessage(callback: WorkerOnMessageCallback): void;

onMessage 监听主线程/Worker 线程向当前线程发送的消息的事件。

# onMessage 兼容性
Web 微信小程序 Android iOS HarmonyOS
- 4.41 - x -
# 参数
名称 类型 必填 默认值 兼容性 描述
callback (message: any) => void -

# onError(callback: WorkerOnErrorCallback): void;

onError 监听 Worker 线程错误事件。当 Worker 线程中发生脚本错误时会触发此事件。

# onError 兼容性
Web 微信小程序 Android iOS HarmonyOS
- 4.41 - x -
# 参数
名称 类型 必填 默认值 兼容性 描述
callback (result: WorkerOnErrorCallbackResult) => void -
# WorkerOnErrorCallbackResult 的属性值
名称 类型 必备 默认值 兼容性 描述
errCode number -
合法值 兼容性 描述
5000501
worker 运行错误
5000502
worker 序列化失败
5000503
worker 实例未运行
5000504
worker 线程中不支持调用的API。
5000505
worker 线程初始化失败
5000506
worker 文件路径无效
5000510
非主线程调用worker API
5000511
worker 线程无效
errSubject string -
统一错误主题(模块)名称
data any -
错误信息中包含的数据
cause Error -
源错误信息,可以包含多个错误,详见SourceError
errMsg string -

# postMessage(message: any, options?: WorkerPostMessageOptions | null): void;

postMessage 向主线程/Worker 线程发送的消息。

# postMessage 兼容性
Web 微信小程序 Android iOS HarmonyOS
- 4.41 - x -
# 参数
名称 类型 必填 默认值 兼容性 描述
message any -
options WorkerPostMessageOptions -
名称 类型 必备 默认值 兼容性 描述
harmonySendable boolean -
是否支持符合Sendable协议的对象作为共享变量发送,使用postMessageWithSharedSendable实现,默认值为false
仅鸿蒙平台支持,参考:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-sendable
transfer Array<any> -
可转移对象数组,默认值为空数组
仅鸿蒙、web平台支持,参考:https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Workers_API/Transferable_objects

# terminate(): void;

terminate 结束当前 Worker 线程。仅限在主线程 worker 对象上调用。

# terminate 兼容性
Web 微信小程序 Android iOS HarmonyOS
- 4.41 - x -

# 示例

示例为hello uni-app x alpha分支,与最新HBuilderX Alpha版同步。与最新正式版同步的master分支示例另见

扫码体验(手机浏览器跳转到App直达页)

示例

<template>
  <scroll-view class="container">
    <view class="status-section">
      <text class="status-label">Worker状态: </text>
      <text class="status-text">{{statusText}}</text>
    </view>

    <view class="button-group">
      <text class="description-text">操作步骤:1.创建Worker 2.添加消息监听 3.发送数据测试</text>
      <button class="btn" type="primary" :disabled="created_boolean" @click="create">创建Worker</button>
      <button class="btn" type="primary" @click="onWorkerMsg">添加消息监听</button>
      <button class="btn" type="primary" @click="onWorkerError">添加错误监听</button>
      <button class="btn" @click="destory" :disabled="workerStatus != 'created'">销毁Worker</button>
    </view>

    <view class="input-section">
      <text class="section-title">输入测试值:</text>
      <text class="description-text">点击发送按钮后,会将输入值传给WorkerTask,在子线程执行+1操作后返回结果</text>
      <input class="input-field" v-model="inputValue" type="number" placeholder="请输入数字" />
      <button class="btn" type="primary" @click="sendMessage" :disabled="workerStatus != 'created'">发送到WorkerTask (值+1)</button>
    </view>

    <view class="log-section">
      <text class="section-title">通信日志:</text>
      <scroll-view class="log-container" scroll-y="true">
        <view v-for="(log, index) in logs" :key="index" class="log-item">
          <text :class="['log-text', log.type]">{{log.message}}</text>
        </view>
      </scroll-view>
      <button @click="clearLogs" class="btn clear-btn">清空日志</button>
    </view>

    <!-- #ifdef APP-HARMONY || WEB -->
    <view class="uni-btn-v">
      <navigator url="/pages/API/create-worker/worker-sendable-transfer">
        <button type="primary">worker sendable transfer 示例</button>
      </navigator>
    </view>
    <!-- #endif -->
  </scroll-view>
</template>

<script>
  export default {
    onUnload() {
      this.destory();
    },
    data() {
      return {
        created_boolean: false,
        workerStatus: 'none', // none, created, destroyed
        isListening: false,
        logs: [] as Array<UTSJSONObject>,
        inputValue: '1', // 默认值为1
        taskResult: '',
        worker: null as Worker | null
      }
    },
    computed: {
      statusText() : string {
        switch (this.workerStatus) {
          case 'none': return '未创建';
          case 'created': return '已创建';
          case 'destroyed': return '已销毁';
          default: return '未知';
        }
      }
    },
    methods: {
      // 添加日志方法
      addLog(message : string, type : string = 'info') {
        const now = new Date();
        const timeStr = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}`;
        const logItem = {
          message: `[${timeStr}] ${message}`,
          type: type,
          time: timeStr
        } as UTSJSONObject;
        this.logs.unshift(logItem);
        // 限制日志数量
        if (this.logs.length > 50) {
          this.logs = this.logs.slice(0, 50);
        }
      },

      // 创建Worker
      create() {
        this.worker = uni.createWorker('workers/helloWorkerTask.uts');
        this.workerStatus = 'created';
        this.addLog('Worker创建成功', 'success');
        this.created_boolean = true;

      },
      onWorkerMsg() {
        if (this.worker == null) {
          this.addLog('请先创建worker', 'warning');
          return;
        }
        this.worker!.onMessage((result) => {
          // 处理Worker返回的消息
          console.log(`收到Worker消息:`, result);
          const res = result as UTSJSONObject;
          const resultData = res['data'] as string;
          this.taskResult = resultData;
          this.inputValue = this.taskResult
          this.addLog(`收到WorkerTask返回: ${resultData}`, 'receive');
        })
      },
      onWorkerError() {
        if (this.worker == null) {
          this.addLog('请先创建worker', 'warning');
          return;
        }
        this.worker!.onError((error) => {
          console.error('Worker发生错误:', error);
          // this.addLog(`Worker错误: ${error.message}`, 'error');
        })
      },

      // 向workerTask发送消息
      sendMessage() {
        // 检查输入值
        if (this.inputValue == '') {
          this.addLog('请输入有效的数字', 'warning');
          return;
        }
        const options = {
          data: this.inputValue,
          needReply: true
        };
        this.worker!.postMessage(options, null);
        this.addLog(`发送值到WorkerTask: ${this.inputValue}`, 'send');
      },

      // 销毁Worker
      destory() {
        if (this.worker == null) {
          this.addLog('没有创建worker,无法销毁', 'warning');
          return;
        }
        this.worker!.terminate();
        this.workerStatus = 'destroyed';
        this.isListening = false;
        this.addLog('Worker已销毁', 'warning');
        this.created_boolean = false;
      },

      // 清空日志
      clearLogs() {
        this.logs = [];
      },

      test_resetInputValue() {
        this.inputValue = '1'
      },
    }
  }
</script>

<style>
  .container {
    flex: 1;
    padding: 10px;
  }

  .status-section {
    display: flex;
    align-items: center;
    justify-content: center;
    margin-bottom: 20px;
    padding: 10px;
    background-color: #ffffff;
    border-radius: 8px;
  }

  .status-label {
    font-size: 16px;
    color: #666666;
  }

  .status-text {
    font-size: 16px;
    font-weight: bold;
    margin-left: 8px;
  }


  .button-group {
    flex-direction: column;
    margin-bottom: 10px;
  }

  .input-section {
    margin-bottom: 20px;
    padding: 15px;
    background-color: #ffffff;
    border-radius: 8px;
  }

  .input-field {
    /* #ifdef MP-WEIXIN */
    height: 3em;
    /* #endif */
    width: 100%;
    padding: 12px;
    border: 1px solid #dddddd;
    border-radius: 6px;
    font-size: 16px;
    margin: 10px 0;
    background-color: #ffffff;
  }

  .btn {
    /*  height: 50px; */
    margin-bottom: 10px;
    padding: 5px 10px;
    border-radius: 6px;
    font-size: 14px;
    text-align: center;
  }

  .log-section {
    background-color: #ffffff;
    border-radius: 8px;
    padding: 15px;
  }

  .section-title {
    font-size: 18px;
    font-weight: bold;
    color: #333333;
    margin-bottom: 10px;
  }

  .log-container {
    height: 300px;
    border: 1px solid #dddddd;
    border-radius: 4px;
    padding: 10px;
    margin: 10px 0;
    background-color: #fafafa;
  }

  .log-item {
    margin-bottom: 5px;
  }

  .log-text {
    font-size: 12px;
    line-height: 1.4;
  }

  .log-text.info {
    color: #2196F3;
  }

  .log-text.success {
    color: #4CAF50;
  }

  .log-text.warning {
    color: #ff9800;
  }

  .log-text.error {
    color: #f44336;
  }

  .log-text.send {
    color: #9C27B0;
  }

  .log-text.receive {
    color: #009688;
  }

  .clear-btn {
    background-color: #ff9800;
    font-size: 12px;
    padding: 8px 12px;
    color: #ffffff;
    border-radius: 4px;
    text-align: center;
  }

  .description-text {
    font-size: 14px;
    color: #666666;
    line-height: 1.4;
    margin-bottom: 10px;
  }
</style>

# 参见

# 通用类型

# GeneralCallbackResult

名称 类型 必备 默认值 兼容性 描述
errMsg string -
错误信息

# Worker 使用流程

# 1. 配置 Worker 信息

worker 代码,是独立的 uts 文件,所有worker代码文件需要放置在专门的目录。在项目的 manifest.json 中可配置 Worker 文件放置的目录:

{
  //...
  "workers": {
    "path": "workers",        // 相对于项目根目录。此配置的意思是在项目根目录下的workers目录下存放worker代码。
    "isSubpackage": true      // 是否分包,默认为 false(仅微信小程序有效)
  }
}

如果不使用微信小程序的分包配置,也可以使用简写配置:

{
  //...
  "workers" : "workers"
}

# 2. 添加 Worker 代码文件

参考上一步的配置,在项目根目录下创建 workers 目录,并创建示例 HelloWorkerTask.uts 文件如下:

	
├─ static
├─ workers                    // Worker 目录
│  └─ HelloWorkerTask.uts     // Worker 代码文件
├─ App.uvue
├─ main.uts
├─ manifest.json
└─ pages.json

# 3. 编写 Worker 代码

Worker 代码中需定义一个类并继承自基类 WorkerTaskImpl,重写 onMessage 方法接收主线程发送的数据。

以下是 HelloWorkerTask.uts 示例代码:

/**
 * HelloWorkerTask
 */
export class HelloWorkerTask extends WorkerTaskImpl {
  /**
   * 构造函数
   */
  constructor() {
    super();
    //初始化操作
    // console.log("构造器初始化");
  }

  /**
   * 实现入口函数
   */
  override entry() {
    //入口函数,Worker 启动时执行
    // console.log("启动完成,等待主线程消息");
  }

  /**
   * 实现接收主线程发送的消息
   */
  override onMessage(message : any) {
    // 处理消息对象
    const messageData = message as UTSJSONObject;

    // console.log('收到主线程数据:', messageData);

    // 发送消息给主线程
    this.postMessageToMain();
  }

  /**
   * 回复消息
   */
  private postMessageToMain() {
    const response = {
      msg: 'message send by worker!'
    };

    // 调用 postMessage 发送消息给主线程
    this.postMessage(response);
  }
}

其中 WorkerTaskImpl 基类定义如下:

/**
 * WorkerTaskImpl
 */
export class WorkerTaskImpl {
  /**
   * 入口函数
   * 可重写修改
   */
  override entry():void;
  /**
   * 接收主线程发送的消息
   * 可重写修改
   */
  override onMessage(message: any): void;

  /**
   * 向主线程发送消息
   */
  postMessage(message: any, options: WorkerPostMessageOptions|null = null): void;
}

/**
 * WorkerPostMessageOptions
 */
export type WorkerPostMessageOptions = {
  /**
   * 是否支持符合Sendable协议的对象作为共享变量发送,使用postMessageWithSharedSendable实现,默认值为false
   * 仅鸿蒙平台支持,参考:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-sendable
   */
  harmonySendable: boolean
  /**
   * 可转移对象数组,默认值为空数组
   * 仅鸿蒙、web平台支持,参考:https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Workers_API/Transferable_objects
   */
  transfer: Array<any>
}

# 4. 主线程中创建 Worker

在主线程的代码中调用 uni.createWorker 创建并返回 Worker 对象,可通过其 onMessage 方法监听 Worker 子线程发送的数据,通过其 onError 方法监听 Worker 子线程的错误。

参考以下示例代码:

// 创建 Worker 实例
const worker = uni.createWorker('workers/HelloWorkerTask.uts');

// 监听 Worker 消息
worker.onMessage((message: any) => {
  const messageData = message as UTSJSONObject;

  console.log('收到Worker子线程数据:', messageData);
});

// 监听 Worker 错误
worker.onError((error: WorkerOnErrorCallbackResult) => {
  console.error('Worker子线程发生错误:', error);
});

# 5. 主线程向 Worker 发送消息

调用 uni.createWorker 创建并返回 Worker 对象的 postMessage 方法向 Worker 子线程发送数据。

参考以下示例代码:

// 向 Worker 子线程发送消息
worker.postMessage({
  msg: 'message send by main!'
});

# 6. harmonySendable 和 transfer 的使用

HarmonyOSWeb 支持 示例:worker Sendable Transfer

# harmonySendable

仅 HarmonyOS 支持。示例:uts-worker-sendable-transfer

  • 在 uts 插件的同级建立 ets 文件,导出 Sendable 对象 示例

    @Sendable
    export class SendableObject {
      a: number = 45;
    }
    
  • 在 uts 插件中引入 注意引入时要有 .ets 后缀 示例

    // #ifdef APP-HARMONY
    import { SendableObject } from './sendable.ets';
    // #endif
    
  • 在 uts 插件中使用,向子线程发送 Sendable 对象 示例

    workerImp.postMessage(new SendableObject())
    
  • 在 worker 中接收到该对象后的修改会直接体现到宿主线程中

# transfer

一个可转移对象数组。示例:worker Sendable Transfer

  • Web 支持 ArrayBuffer[]、MessagePort[]、ImageBitmap[] 等。 可转移对象
  • HarmonyOS 仅支持 ArrayBuffer[]

注意事项

  • HarmonyOS 平台上使用 transfer 后,转移的 ArrayBuffer 长度不会改变,数组本身变为在宿主线程不可用。鸿蒙文档
  • Web 平台使用 transfer 后,转移的数组长度会变为 0。MDN

# 7. 结束 Worker

Worker 线程不再使用需主动结束释放相关资源,调用 Worker 对象的 terminate 方法结束子线程。

参考以下示例代码:

// 结束 Worker 子线程
worker.terminate();

# Tips

  • uni.createWorkder 仅支持在主线程中使用,在 Worker 子线程中使用会返回错误
  • 各平台在 Worker 中使用全局变量或静态属性在内存管理中存在差异,Android/iOS平台可以共享内存,其它平台不能共享,为了避免这些差异带来的影响建议不要使用全局变量和静态属性
  • Worker 子线程间暂不支持直接互相通讯,如要通讯可通过主线程中转发送消息来实现
  • Android/iOS平台主线程与 Worker 线程传输的引用类型数据是直接共享使用(其它平台是默认为复制),需避免并发访问,暂未提供线程间安全访问机制,需通过业务逻辑控制避免并发访问这些共享的数据
  • 鸿蒙平台主线程与 Worker 线程传输的数据默认为浅拷贝,如需传出共享对象,可在uts插件中混编开发定义Sendable对象,调用 Worker.postMessage 发送这些共享对象时设置 harmonySendable 参数为 true
  • iOS平台 Worker 仅支持在uts插件中使用,不能直接在 uvue 页面中调用 uni.createWorkder
  • Worker 中仅支持调用界面无关的API(如 uni.request、uni.getLocation 等),这些 API 触发的回调运行在 Workder 线程中
  • Web 平台不支持在 worker 中调用 uni 上的 API