自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

React中使用多線程—Web Worke

開發(fā) 前端
眾所周知,Javascript是一種「單線程語言」。因此,如果我們執(zhí)行任何耗時(shí)任務(wù),它將阻塞UI交互。用戶需要等待任務(wù)完成才能執(zhí)行其他操作,這會(huì)給用戶體驗(yàn)帶來不好的影響。

前言

作為一個(gè)前端開發(fā),如果你還停留在每天CRUD,還停留在切圖/畫圖,還停留在和后端同學(xué)對某個(gè)API設(shè)計(jì)的是否合理而大打出手時(shí),是時(shí)候停下來了。我們要變強(qiáng),我們需要對我們經(jīng)手的項(xiàng)目進(jìn)行一番改造和優(yōu)化。這才是我們能夠變強(qiáng)的方式。而不是,沉浸在無休止的爭吵和埋怨中。

眾所周知,Javascript是一種「單線程語言」。因此,如果我們執(zhí)行任何耗時(shí)任務(wù),它將阻塞UI交互。用戶需要等待任務(wù)完成才能執(zhí)行其他操作,這會(huì)給用戶體驗(yàn)帶來不好的影響。

其實(shí),針對此類問題,我們有很多解決方案,

  • 例如將耗時(shí)任務(wù)分割成多個(gè)短任務(wù),并讓其在多個(gè)渲染幀內(nèi)執(zhí)行,給UI交互(也就是UI渲染)留有時(shí)間,
  • 也可以通過回調(diào)的方式,在UI交互觸發(fā)后,在進(jìn)行耗時(shí)任務(wù)的操作。
  • 亦或者我們可以指定一個(gè)「優(yōu)先隊(duì)列」,當(dāng)高優(yōu)先級任務(wù)被執(zhí)行時(shí),低優(yōu)先級任務(wù)(耗時(shí)任務(wù))被降級處理(冷處理),直到高優(yōu)先級任務(wù)被執(zhí)行后再執(zhí)行剩余低優(yōu)先級任務(wù)。(這其實(shí)就是React并發(fā)的核心要點(diǎn))
  • ...等等

上述列舉了很多解決方式,他們都有一個(gè)共同特點(diǎn) - 由于JS單線程屬性,它們只是將一些耗時(shí)任務(wù)從一個(gè)渲染幀分割或者延后到多個(gè)渲染幀內(nèi)。本質(zhì)上還是單線程的處理方式。

而,今天我們就介紹一種利用「多線程(Web Worker)處理React中的耗時(shí)操作」。我們之前也在前面講過Web Worker的相關(guān)內(nèi)容。

  • Web性能優(yōu)化之Worker線程(上)
  • Web性能優(yōu)化之Worker線程(下)

今天我們就詳細(xì)的介紹如何在前端項(xiàng)目中使用Web Worker用于處理耗時(shí)任務(wù),然后將長任務(wù)利用多線程的分割出主線程,然后給主線程留足時(shí)間去回應(yīng)更緊急的用戶操作,優(yōu)化用戶操作。

好了,天不早了,干點(diǎn)正事哇。

我們能所學(xué)到的知識點(diǎn)

  1. Web Workers
  2. React 的并發(fā)模式

React 中使用Web Worker

  1. useWorker
  2. Web Worker的注意點(diǎn)

1. Web Workers

雖然,在之前的文章中介紹過Web Worker,但是為了最大限度的兼容大家的學(xué)習(xí)情況,還是打算簡單介紹一些。

圖片圖片

如上圖所示,JS中存在三中Worker,按照實(shí)現(xiàn)可以分為三類。

  1. Web Worker
  2. Shared Web Worker
  3. Service Worker

而我們今天的主角-Web Worker是我們最常見的。

Web Worker是在后臺運(yùn)行的腳本,不會(huì)影響用戶界面,因?yàn)樗凇竼为?dú)的線程中運(yùn)行」,而不是在主線程中。

因此,它不會(huì)導(dǎo)致任何阻塞用戶交互。Web Worker主要用于在Web瀏覽器中執(zhí)行耗時(shí)任務(wù),如對大量數(shù)據(jù)進(jìn)行排序、CSV導(dǎo)出、圖像處理等。

圖片圖片

從上圖中,如果耗時(shí)任務(wù)在主線程中執(zhí)行會(huì)阻塞UI渲染,當(dāng)用Web Worker代理耗時(shí)任務(wù)后,主線程并不會(huì)發(fā)生阻塞,也就是說「它強(qiáng)任它強(qiáng),老子Web Worker」

2. React 的并發(fā)模式

講到這里,可能有些心細(xì)的小伙伴就會(huì)產(chǎn)生疑問。既然都是處理耗時(shí)任務(wù)。那么,React 18的并發(fā)渲染也可以達(dá)到此種目的。也就是使用React.useTransition()將耗時(shí)任務(wù)設(shè)定為過渡任務(wù),通過對某些操作標(biāo)記為「低優(yōu)先級」,在頁面渲染過程中給「高優(yōu)先級」的任務(wù)讓步。

之前我們在

  • React 18 如何提升應(yīng)用性能
  • React 并發(fā)原理

中,對React 并發(fā)有過介紹。(想了解更多可以翻閱上述文章)。這里我們就簡單闡述一下為什么React 并發(fā)只是錦上添花,缺不能藥到病除。

如果,你仔細(xì)看過上面的文章,你就會(huì)有有一個(gè)清晰的認(rèn)知:

React并發(fā)模式并不會(huì)并行運(yùn)行任務(wù)。它會(huì)將非緊急任務(wù)移動(dòng)到過渡狀態(tài),并立即執(zhí)行緊急任務(wù)。它「使用相同的主線程」來處理它。

下面是之前的一個(gè)示例。

圖片圖片

使用useTransition只是告知React,有一些操作是不緊急的,如果遇到更高級的任務(wù),不緊急的任務(wù)可以不立馬顯示,而是在處理完高優(yōu)先級任務(wù)后才進(jìn)行低優(yōu)先級任務(wù)的渲染。

圖片圖片

例如,如果一個(gè)表格正在渲染一個(gè)大型數(shù)據(jù)集,而用戶嘗試搜索某些內(nèi)容,React會(huì)將任務(wù)切換到用戶搜索并首先處理它。

圖片圖片

正如我們在圖片中看到的那樣,

「緊急任務(wù)是通過上下文切換」來處理的

React的并發(fā)模式,只是讓我們的項(xiàng)目「擁有了辨別優(yōu)先級的能力」,并且在「一定限制條件下」能夠快速響應(yīng)用戶操作。但是,但是,但是,如果一個(gè)「單個(gè)任務(wù)已經(jīng)超過了瀏覽器一幀的渲染時(shí)間」,那雖然設(shè)置了startTransition,但是也「無能為力」。如果存在這種情況,那就只能人為的將單個(gè)任務(wù)繼續(xù)拆分或者利用Web Worker進(jìn)行多線程處理了。

當(dāng)使用Web Worker進(jìn)行相同任務(wù)時(shí),表格渲染會(huì)在一個(gè)獨(dú)立的線程中并行運(yùn)行。

圖片圖片

3. React 中使用Web Worker

由于我們在項(xiàng)目開發(fā)時(shí),使用不同的打包工具(vite/webpack)。幸運(yùn)的是,最新版的vite/webpack都支持Web Worker了。

我們可以通過

  • new URL()的方式 --vite/webpack都支持
new Worker(
  new URL(
    './worker.js', 
    import.meta.url
  )
);
  • import方式 只有vite支持
import MyWorker from './worker?worker'

const worker = new MyWorker()

更詳細(xì)的處理可以參考它們的官網(wǎng)

  • vite_web_worker[1]
  • webpack_web_worker[2]

當(dāng)然,我們在項(xiàng)目代碼中如何實(shí)例化Worker對象也有很多方式。下面就介紹兩種。

通過引入文件路徑

index.js

// 創(chuàng)建一個(gè)新的Worker對象,
 // 指定要在Worker線程中執(zhí)行的腳本文件路徑
  const myWorker = new Worker(
    new URL('./worker.js', import.meta.url)
    );

  // 向Worker發(fā)送消息
  myWorker.postMessage(789789);

  // 監(jiān)聽來自Worker的消息
  myWorker.onmessage = function(event) {
    console.log("來自worker的消息: ", event.data);
  };

worker.js

// 在Worker腳本中接收并處理消息
self.onmessage = function(event) {
    console.log("來自主線程的消息: ", event.data);
    // 執(zhí)行一些計(jì)算密集型的任務(wù)
    let result = doSomeHeavyTask(event.data);
    // 將結(jié)果發(fā)送回主線程
    self.postMessage(result);
};

const doSomeHeavyTask = (num) => {
  // 模擬一些計(jì)算密集型的操作
  let result = 0;
  for (let i = 0; i < num; i++) {
    result += i;
  }
  return result;
};

Blob 方式

index.js

// 定義要在Worker中執(zhí)行的腳本內(nèi)容
  const workerScript = `
    self.onmessage = function(e) {
      console.log('來自主線程的消息: ' + e.data);
      self.postMessage('向主線程發(fā)送消息: ' + 'Hello, ' + e.data);
    };
  `;

  // 創(chuàng)建一個(gè)Blob對象,指定腳本內(nèi)容和類型
  const blob = new Blob(
    [workerScript], 
    { type: 'application/javascript' }
  );

  // 使用URL.createObjectURL()方法創(chuàng)建一個(gè)URL,用于生成Worker
  const blobURL = URL.createObjectURL(blob);

  // 生成一個(gè)新的Worker
  const worker = new Worker(blobURL);

  // 監(jiān)聽來自Worker的消息
  worker.onmessage = function(e) {
    console.log('來自worker的消息: ' + e.data);
  };

  // 向Worker發(fā)送消息
  worker.postMessage('Front789');

使用Blob構(gòu)建方式生成Web Worker有以下幾個(gè)優(yōu)勢:

優(yōu)勢

描述

動(dòng)態(tài)生成

可以動(dòng)態(tài)地生成Worker腳本,無需保存為單獨(dú)文件,根據(jù)需要生成不同的Worker實(shí)例。

內(nèi)聯(lián)腳本

Worker腳本嵌入到Blob對象中,直接在JavaScript代碼中定義Worker的邏輯,無需外部腳本文件。

便捷性

更方便地創(chuàng)建和管理Worker實(shí)例,無需依賴外部文件。

安全性

Blob對象在內(nèi)存中生成,不需要保存為實(shí)際文件,提高安全性,避免了對實(shí)際文件的依賴和管理。

總的來說,使用Blob構(gòu)建方式生成Web Worker可以提供更靈活、便捷和安全的方式來管理和使用Worker實(shí)例。

4. useWorker

上面一節(jié)中,我們介紹了如何在前端項(xiàng)目中使用Web Worker。無論是使用文件導(dǎo)入的方式還是Blob的方式。都需要寫一些模板代碼。雖然能解決我們的問題,但是使用方式還是不夠優(yōu)雅。

功能介紹

下面,我們就介紹一種更優(yōu)雅的方式- 使用useWorker庫。

useWorker[3]是一個(gè)庫,它使用React Hooks在簡單的配置中使用Web Worker API。它支持在不阻塞UI的情況下執(zhí)行耗時(shí)任務(wù),支持使用Promise而不是事件監(jiān)聽器。

我們可以從官網(wǎng)看到相關(guān)的介紹信息。

圖片圖片

其中,WORKER_STATUS用于返回Web Worker的狀態(tài)信息。

圖片圖片

我們可以通過向useWorker中傳遞一個(gè)回調(diào)函數(shù),然后該函數(shù)就會(huì)在對應(yīng)的Web Worker中執(zhí)行。

const sortNumbers = numbers => ([...numbers].sort())
const [
  sortWorker, 
  { 
    status: sortStatus, 
    kill: killSortWorker 
  }
  ] = useWorker(sortNumbers);

大家可以對比之前的用原生構(gòu)建Web Worker實(shí)例。我們可以拋棄冗余代碼,并且返回的函數(shù)(sortWorker)還支持Promise。

也就意味著我們使用xx.then()或者 await xx()以同步的寫法獲取異步結(jié)果。

import React from "react";
import { useWorker } from "@koale/useworker";

const numbers = [...Array(5000000)].map(
  e => ~~(Math.random() * 1000000)
);
const sortNumbers = nums => nums.sort();

const Example = () => {
  const [sortWorker] = useWorker(sortNumbers);

  const runSort = async () => {
    const result = await sortWorker(numbers); 
  };

  return (
    <button type="button" onClick={runSort}>
      運(yùn)行耗時(shí)任務(wù)
    </button>
  );
};

并且,useWorker是一個(gè)大小為3KB的庫,我們還不需要有太多的資源負(fù)擔(dān)。既然,有這么多強(qiáng)勢的功能,那我們就來看看它到底是何方神圣。

安裝依賴

用我們御用腳手架f_cli[4],來構(gòu)建一個(gè)前端項(xiàng)目(npx f_cli_f craete worker_demo)。

要將useWorker()添加到React項(xiàng)目中,請使用以下命令:

npm  install @koale/useworker --force

由于useworker源碼中使用了peerDependencies指定了React版本為^16.8.0。如果大家在17/18版本的React環(huán)境下,會(huì)發(fā)生錯(cuò)誤。所以我們可以使用--force忽略版本限制。(這里大家可以放心使用,它內(nèi)部的只是用到簡單的hook)

安裝完包后,導(dǎo)入useWorker()。

import { 
  useWorker, 
  WORKER_STATUS 
  } from "@koale/useworker";

我們從庫中導(dǎo)入useWorker和WORKER_STATUS。useWorker()鉤子返回workerFn和controller。

  • workerFn是一個(gè)允許在Web Worker中運(yùn)行函數(shù)的函數(shù)。
  • controller包含status和kill參數(shù)。

status參數(shù)返回Worker的狀態(tài)

kill函數(shù)用于終止當(dāng)前運(yùn)行的Worker

案例展示

讓我們通過一個(gè)示例來看看useWorker()。

使用useWorker()和主線程對大數(shù)組進(jìn)行排序

SortingArray

首先,創(chuàng)建一個(gè)SortingArray組件,并添加以下代碼:

工具代碼
// 模擬耗時(shí)任務(wù)
const bubleSort = (arr: number[]): number[] =>{
  const len = arr.length;

  for (let i = 0; i < len; i++) {
    for (let j = 0; j < len - 1 - i; j++) {
      if (arr[j] > arr[j + 1]) {
        [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
      }
    }
  }

  return arr;
}
const numbers = [...Array(50000)].map(() =>
  Math.floor(Math.random() * 1000000)
);
主要邏輯
import React,{ useState } from "react";
import { 
  useWorker, 
  WORKER_STATUS 
  } from "@koale/useworker";

function SortingArray() {
  const [sortStatus, setSortStatus] = useState(false);
  const [
    sortWorker, 
    { 
      status: sortWorkerStatus 
    }
    ] = useWorker(bubleSort);
    
  console.log("WebWorker status:", sortWorkerStatus);
  
  const onSortClick = () => {
    setSortStatus(true);
    const result = bubleSort(numbers);
    setSortStatus(false);
    alert('耗時(shí)任務(wù)結(jié)束!')
    console.log("處理結(jié)果", result);
  };

  const onWorkerSortClick = () => {
    sortWorker(numbers).then((result) => {
      console.log("使用WebWorker的處理結(jié)果", result);
      alert('耗時(shí)任務(wù)結(jié)束!')
    });
  };

  return (
    <div>
      <section >
        <button
          type="button"
          disabled={sortStatus}
          onClick={() => onSortClick()}
        >
          {sortStatus ? 
            `正在處理耗時(shí)任務(wù)...` :
            `主線程觸發(fā)耗時(shí)任務(wù)`
          }
        </button>
        <button
          type="button"
          disabled={sortWorkerStatus === WORKER_STATUS.RUNNING}
          onClick={() => onWorkerSortClick()}
        >
          {sortWorkerStatus === WORKER_STATUS.RUNNING
            ? `正在處理耗時(shí)任務(wù)...`
            : `使用WebWorker處理耗時(shí)任務(wù)`
          }
        </button>
      </section>
      <section>
        <span style={{ color: "white" }}>
          打開控制臺查驗(yàn)狀態(tài)信息
        </span>
      </section>
    </div>
  );
}

export default SortingArray;

我們在SortingArray配置了兩個(gè)操作

  1. onSortClick中按照常規(guī)處理,也就是在主線程中執(zhí)行耗時(shí)操作
  2. onWorkerSortClick 中執(zhí)行useWorker相關(guān)邏輯,并傳遞了bubleSort函數(shù)以使用Worker執(zhí)行耗時(shí)的排序操作。

App.js

我們App.js中引入SortingArray組件,并且為了能讓UI阻塞看的更明顯,我們用JS來操作logo文件,讓其不停的轉(zhuǎn)動(dòng),每100毫秒旋轉(zhuǎn)一次。

  • 如果是一個(gè)阻塞主線程的任務(wù),那么logo將會(huì)停止
  • 如果主線程不阻塞,那logo會(huì)一直轉(zhuǎn)動(dòng)
import React from "react";
import SortingArray from "./SortingArray";
import logo from './assets/react.svg'
import "./App.css";

let turn = 0;

function infiniteLoop() {
  const lgoo = document.querySelector(".logo");
  turn += 8;
  lgoo.style.transform = `rotate(${turn % 360}deg)`;
}

export default function App() {

  React.useEffect(() => {
    const loopInterval = setInterval(infiniteLoop, 100);
    return () => clearInterval(loopInterval);
  }, []);

  return (
    <>
      <div >
        <h1 >useWorker Demo</h1>
        <header>
          <img src={logo} className="logo" />
        </header>
        <hr />
      </div>
      <div>
        <SortingArray />
      </div>
    </>
  );
}

我們來看看分別點(diǎn)擊對應(yīng)按鈕會(huì)發(fā)生啥?

上圖是耗時(shí)任務(wù)在主線程中執(zhí)行的效果。在執(zhí)行期間,動(dòng)畫效果是阻塞的,也就意味著在多個(gè)幀的時(shí)間內(nèi),瀏覽器是無法執(zhí)行額外的操作的。

我們用Chrome-performance來探查一下性能消耗。

我們可以看到事件:點(diǎn)擊任務(wù)花費(fèi)了7.85秒來完成整個(gè)過程,并且它阻塞了主線程7.85秒。

圖片圖片

而這個(gè)圖,我們使用了Web Worker,在執(zhí)行耗時(shí)任務(wù)的時(shí)候,動(dòng)畫還是執(zhí)行原來的操作。也就是操作不會(huì)阻塞。因?yàn)閡seWorker在后臺執(zhí)行排序而不阻塞UI。這使得用戶體驗(yàn)非常流暢。

和上面的分析方式一樣,打開Performancetab,讓我們看看這種方法的性能分析結(jié)果。

圖片圖片

我們截取主線程的部分?jǐn)?shù)據(jù),發(fā)現(xiàn)有任意時(shí)間段內(nèi),Scripting所占總時(shí)間的比例都很少,更大部分都是Idle也就是主線程處于空閑階段,可以隨時(shí)響應(yīng)用戶操作。

圖片圖片

而在對應(yīng)的worker中確是一直在執(zhí)行計(jì)算任務(wù),絲毫沒有片刻休息。

5. Web Worker的注意點(diǎn)

何時(shí)用Worker

我們之前的文章講過,JS自從引入V8[5]后,在代碼執(zhí)行和內(nèi)存處理上有了更高的優(yōu)化。例如使用JIT[6],引入WebAssembly[7],熱代碼優(yōu)先編譯等。

但是呢,針對一些特殊的場景,上述的方式只能提供簡單的優(yōu)化,這樣我們就需要另外的解決方案來處理這些棘手的問題。

當(dāng)我們遇到如下情景,并有嚴(yán)重的性能問題,那就需要借助Web Worker一臂之力了

  • 圖像處理
  • 對大型數(shù)據(jù)集進(jìn)行排序或處理
  • 帶有大量數(shù)據(jù)的CSV或Excel導(dǎo)出
  • 畫布繪制
  • 任何CPU密集型任務(wù)

Worker的限制

這個(gè)在之前介紹Web Worker的文章就介紹過,我們就直接拿來主義了。

  • Web Worker無法訪問window對象和document。
  • 當(dāng)Worker正在運(yùn)行時(shí),我們無法再次調(diào)用它,直到它完成或被終止。為了解決這個(gè)問題,我們可以創(chuàng)建兩個(gè)或更多useWorker()鉤子的實(shí)例。
  • Web Worker無法返回函數(shù),因?yàn)轫憫?yīng)是序列化的。
  • Web Worker受到終端用戶機(jī)器可用CPU核心和內(nèi)存的限制。

Reference

[1]vite_web_worker:https://cn.vitejs.dev/guide/features.html#web-workers

[2]webpack_web_worker:https://webpack.js.org/guides/web-workers/

[3]useWorker:https://github.com/alewin/useWorker

[4]f_cli:https://www.npmjs.com/package/f_cli_f

[5]V8:https://v8.dev/

[6]JIT:https://v8.dev/blog/maglev

[7]WebAssembly:https://webassembly.github.io/spec/core/

責(zé)任編輯:武曉燕 來源: 前端柒八九
相關(guān)推薦

2024-01-07 17:29:10

編程語言線程Rust

2011-08-31 16:30:19

Lua多線程

2023-12-01 09:18:27

AxiosAxios 庫

2009-10-13 09:56:13

.NET多線程程序

2016-11-10 16:30:22

Java多線程

2009-03-18 09:26:23

Winform多線程C#

2011-08-23 17:52:39

LUAWeb 開發(fā)

2021-05-31 17:37:26

ViteReactesbuild

2023-05-24 16:41:41

React前端

2017-07-04 19:02:17

ReacRedux 項(xiàng)目

2021-09-11 15:26:23

Java多線程線程池

2009-06-29 18:00:05

Java多線程Runnable接口創(chuàng)建線程

2011-04-14 13:27:53

Synchronize多線程

2009-09-22 17:21:24

線程局部變量

2022-11-30 15:01:11

React技巧代碼

2022-06-10 08:01:17

ReduxReact

2024-11-25 07:00:00

箭頭函數(shù)JavaScriptReact

2009-03-12 10:52:43

Java線程多線程

2021-09-07 10:24:36

Vue應(yīng)用程序Web Workers

2011-12-25 15:37:51

ibmdw
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號