使用 useWorker 在單獨的執行緒中,處理昂貴且阻塞 UI 的任務。

眾所周知,Javascript 是一種單線程語言。所以,做任何昂貴的任務,它都會阻塞 UI 互動。用戶需要等到它完成,才能執行剩餘的其他操作,這會帶來糟糕的用戶體驗。

為了克服和執行這些任務,Javascript 有一個名為 Web Workers 的解決方案,它允許在 web 中執行消耗效能的任務時,瀏覽器不會阻塞用戶界面,使用戶體驗非常流暢。

本篇文章簡單介紹這個 hook。

原文出處:https://dev.to/nilanth/multi-threaded-react-app-using-useworker-gf8


Web Workers

Web Worker 是一個在後台執行而不影響用戶界面的腳本,因為它在一個單獨的線程而不是主線程中執行。所以它不會對用戶交互造成任何阻塞。 Web Worker 主要用於在 Web 瀏覽器中執行昂貴的任務,例如對大量資料進行排序、CSV 導出、圖像處理等。

工作線程

參考上圖,我們可以看到一個昂貴的任務是由一個工作線程並行執行的,而不會阻塞主線程。當我們在主線程中執行相同的操作時,會導致 UI 阻塞。

React 中的 Concurrent mode 呢?

React 並發模式 不會並行執行任務。它將非緊急任務轉移,接著立即執行緊急任務。它使用相同的主線程來處理。

正如我們在下圖中看到的,緊急任務是使用上下文切換來處理的。例如,如果一個表正在呈現一個大型資料集,並且用戶試圖搜尋某些內容,React 會將任務切換為用戶搜尋,並首先處理它,如下所示。

任務切換

當為同一任務使用 worker 時,表格渲染在一個單獨的線程中並行執行。檢查下圖。

反應工作者

useWorker

useWorker 是一個通過 React Hooks 在簡單配置中使用 Web Worker API 的函式庫。它支持在不阻塞 UI 的情況下執行昂貴任務,支援 promise 而不是事件監聽器,還有一些值得注意的功能:

  1. 結束超時 worker 的選項

  2. 遠程依賴

  3. Transferable

  4. Worker status

useWorker 是一個 3KB 的庫

為什麼不用 JavaScript 內置 web worker?

在使用 javascript web worker 時,我們需要加入一些複雜的配置,來設置多行程式碼的 worker。使用 useWorker,我們可以簡化 worker 的設置。讓我們在下面的程式碼中,看看兩者之間的區別。

在 React App 中使用 JS Web Worker 時

// webworker.js

self.addEventListener("message", function(event) {
  var numbers = [...event.data]
  postMessage(numbers.sort())
});

//index.js

var webworker = new Worker("./webworker.js");

webworker.postMessage([3,2,1]);

webworker.addEventListener("message", function(event) {
    console.log("Message from worker:", event.data); // [1,2,3]
});

在 React App 中使用 useWorker 時

    // index.js

    const sortNumbers = numbers => ([...numbers].sort())
    const [sortWorker] = useWorker(sortNumbers);

    const result = await sortWorker([1,2,3])

正如我之前所說,與普通的 javascript worker 相比,useWorker() 簡化了配置。

讓我們與 React App 整合並執行高 CPU 密集型任務,來看看 useWorker() 的實際執行情況。

Quick Start

要將 useWorker() 加入到 React 專案,請使用以下命令

yarn add @koale/useworker

安裝套件後,導入 useWorker()。

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

我們從函式庫中導入 useWorker 和 WORKER_STATUS。 useWorker() 鉤子返回 workerFn 和 controller。

  1. workerFn 是允許使用 web worker 執行函數的函數。

2.controller 由 status 和 kill 參數組成。 status 參數返回 worker 的狀態和用於終止當前執行的 worker 的 kill 函數。

讓我們用一個例子來看看 useWorker()

使用 useWorker() 和主線程對大型陣列進行排序

首先建立一個SortingArray元件,加入如下程式碼

    //Sorting.js

    import React from "react";
    import { useWorker, WORKER_STATUS } from "@koale/useworker";
    import { useToasts } from "react-toast-notifications";
    import bubleSort from "./algorithms/bublesort";

    const numbers = [...Array(50000)].map(() =>
      Math.floor(Math.random() * 1000000)
    );

    function SortingArray() {
      const { addToast } = useToasts();
      const [sortStatus, setSortStatus] = React.useState(false);
      const [sortWorker, { status: sortWorkerStatus }] = useWorker(bubleSort);
      console.log("WORKER:", sortWorkerStatus);
      const onSortClick = () => {
        setSortStatus(true);
        const result = bubleSort(numbers);
        setSortStatus(false);
        addToast("Finished: Sort", { appearance: "success" });
        console.log("Buble Sort", result);
      };

      const onWorkerSortClick = () => {
        sortWorker(numbers).then((result) => {
          console.log("Buble Sort useWorker()", result);
          addToast("Finished: Sort using useWorker.", { appearance: "success" });
        });
      };

      return (
        <div>
          <section className="App-section">
            <button
              type="button"
              disabled={sortStatus}
              className="App-button"
              onClick={() => onSortClick()}
            >
              {sortStatus ? `Loading...` : `Buble Sort`}
            </button>
            <button
              type="button"
              disabled={sortWorkerStatus === WORKER_STATUS.RUNNING}
              className="App-button"
              onClick={() => onWorkerSortClick()}
            >
              {sortWorkerStatus === WORKER_STATUS.RUNNING
                ? `Loading...`
                : `Buble Sort useWorker()`}
            </button>
          </section>
          <section className="App-section">
            <span style={{ color: "white" }}>
              Open DevTools console to see the results.
            </span>
          </section>
        </div>
      );
    }

    export default SortingArray;

這裡我們配置了 useWorker 並傳遞了 bubleSort 函數來使用 worker 執行昂貴的排序。

接下來,將以下程式碼加入到 App.js 元件,並導入 SortingArray 元件。

    //App.js

    import React from "react";
    import { ToastProvider } from "react-toast-notifications";
    import SortingArray from "./pages/SortingArray";
    import logo from "./react.png";
    import "./style.css";

    let turn = 0;

    function infiniteLoop() {
      const lgoo = document.querySelector(".App-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 (
        <ToastProvider>
          <div className="App">
            <h1 className="App-Title">useWorker</h1>
            <header className="App-header">
              <img src={logo} className="App-logo" alt="logo" />
              <ul>
                <li>Sorting Demo</li>
              </ul>
            </header>
            <hr />
          </div>
          <div>
            <SortingArray />
          </div>
        </ToastProvider>
      );
    }

我們已將 React 徽標加入到 App.js 元件,該組件每 100ms 旋轉一次,以直觀地表示執行昂貴任務時的阻塞和非阻塞 UI。

執行上面的程式碼時,我們可以看到兩個按鈕 Normal Sort 和 Sort using useWorker()

接下來,單擊 Normal Sort 按鈕在主線程中對陣列進行排序。我們可以看到 React 徽標停止旋轉幾秒鐘。由於排序任務阻塞了UI渲染,所以排序完成後logo又開始旋轉了。這是因為兩個任務都在主線程中處理。檢查以下gif

正常排序

讓我們使用 chrome 性能記錄 檢查其性能分析。

性能正常

我們可以看到 Event: click 任務用了 3.86 秒 來完成這個過程,它阻塞了主線程 3.86 秒。

接下來,讓我們嘗試使用 useWorker() 選項進行排序。點擊它的時候我們可以看到 react logo 還在不間斷的旋轉。由於 useWorker 在不阻塞 UI 的情況下在後台執行排序。這使得用戶體驗非常流暢。檢查以下gif

工人排序

您還可以使用 sortWorkerStatus 在控制台中看到 worker 狀態為 RUNNINGSUCCESS

讓我們看看這種方法的性能分析結果

主線程

工作線程

正如我們所看到的,第一張圖片表示主線程中沒有長時間執行的進程。在第二張圖片中,我們可以看到排序任務由一個單獨的工作線程處理。所以主線程沒有阻塞任務。

您可以在以下沙箱中試用整個範例。

https://codesandbox.io/embed/useworker-sorting-example-041qhc?fontsize=14&hidenavigation=1&theme=dark

何時使用worker

1.圖像處理

  1. 排序或處理大資料集。

  2. 大資料CSV或Excel導出。

  3. 畫布繪圖

  4. 任何 CPU 密集型任務。

useWorker 的限制

  1. web worker 無權存取 window 物件和 document。

  2. 當 worker 正在執行時,我們不能再次呼叫它,直到它完成或被中止。為了解決這個問題,我們可以建立兩個或多個 useWorker() 鉤子實例。

  3. Web Worker 無法返回函數,因為響應是序列化的。

  4. Web Workers 受限於最終用戶機器的可用 CPU 內核和內存。

結論

Web Worker 允許在 React 應用程式中使用多線程來執行昂貴的任務而不會阻塞 UI。而 useWorker 允許在 React 應用程式中以簡化的掛鉤方法使用 Web Worker API。 Worker 不應該被過度使用,我們應該只在必要時使用它,否則會增加管理 Worker 的複雜性。


共有 0 則留言