TypeScriptにおけるWeb Worker

TypeScriptにおけるWeb Worker

この記事ではTypeScriptにおけるWeb Workerについて説明します。

Web Workerの概念から様々な使い方のポイントをサンプルと共に学べます。

YouTube Video

TypeScriptにおけるWorker

TypeScriptにおけるWorkerは、JavaScriptのWeb Workers APIを活用して、メインスレッドとは別にバックグラウンドで処理を行うための仕組みです。これにより、重い計算や非同期タスクがユーザーインターフェースの操作に影響を与えることなく実行できるようになります。

Workerはメインスレッド(UIスレッド)と並行して動作し、メッセージを通じてスレッド間でデータをやり取りすることができます。TypeScriptでも、型安全なコードを書きつつWorkerを使用することができます。

基本的なWorkerの使い方

  1. Workerの作成

    Workerインスタンスを作成し、指定したスクリプトを実行します。通常、スクリプトは別ファイルで定義します。

  2. メッセージのやり取り

    メインスレッドとWorkerスレッドの間で、postMessageonmessageを使ってメッセージを送受信します。

例: 基本的なWorkerの実装

  1. worker.ts: Worker用スクリプト
1// worker.ts
2self.onmessage = (event) => {
3    const data = event.data;
4    const result = data.num1 + data.num2;
5    self.postMessage(result); // Return the result to the main thread
6};
  1. main.ts: メインスレッドでWorkerを利用するスクリプト
 1// main.ts
 2const worker = new Worker(
 3    new URL('./worker.ts', import.meta.url),
 4    { type: 'module' }
 5);
 6
 7worker.onmessage = (event) => {
 8    console.log("Result from worker:", event.data); // Receive message from the worker
 9};
10
11worker.postMessage({ num1: 10, num2: 20 }); // Send message to the worker
  • この例では、worker.tsは別スレッドで動作し、num1num2の和を計算してメインスレッドに返します。メインスレッドは、その結果を受け取ってコンソールに出力します。
  • type: 'module' を指定すると、Worker スクリプトが ESモジュール として解釈され、importexport が利用可能になります。これにより従来の importScripts() を使わずにモジュール構造を扱えます。

TypeScriptでWorkerを使う際のポイント

型定義の追加

TypeScriptでは、メッセージの送受信時に型安全なやり取りを行うために、データの型を定義します。

 1// Define data types
 2interface WorkerData {
 3    num1: number;
 4    num2: number;
 5}
 6
 7interface WorkerResult {
 8    result: number;
 9}
10
11// worker.ts
12self.onmessage = (event: MessageEvent<WorkerData>) => {
13    const data = event.data;
14    const result = data.num1 + data.num2;
15    const message: WorkerResult = { result };
16    self.postMessage(message); // Send the result in a type-safe manner
17};
18
19// main.ts
20const worker = new Worker(new URL('./worker.ts', import.meta.url), { type: 'module' });
21
22// Type the event from the worker
23worker.onmessage = (event: MessageEvent<WorkerResult>) => {
24  console.log("Result from worker:", event.data); // event.data is number
25};
26
27// Send typed data to the worker
28const message: WorkerData = { num1: 10, num2: 20 };
29worker.postMessage(message);
  • MessageEventの型パラメータを指定することで、受け取るデータの型を明確に定義できます。これにより、型安全にデータをやり取りできます。

WebpackやViteの設定

TypeScriptでWorkerを使う場合、WebpackViteのようなバンドラが必要なことがあります。これらのツールを使うことで、Workerスクリプトを適切にバンドルし、メインスレッドから利用できるようにします。

たとえば、Viteを使う場合、import.meta.urlを使って正しくWorkerをインポートします。

1const worker = new Worker(new URL('./worker.ts', import.meta.url), { type: 'module' });
  • これにより、バンドルされたWorkerスクリプトが正しく読み込まれ、Workerを活用した処理が可能になります。

メッセージングと並行処理の注意点

  1. データのコピー

    メインスレッドとWorkerスレッドの間でメッセージを送受信する際、データはコピーされます。オブジェクトなどの複雑なデータを扱う場合、効率を考慮する必要があります。大きなデータを頻繁にやり取りすると、パフォーマンスが低下する可能性があります。

  2. Transferableオブジェクト

    ArrayBuffer のような一部のオブジェクトは Transferableオブジェクトと呼ばれます。Transferableオブジェクトは、メッセージング時にコピーされるのではなく、所有権をWorker側に譲渡することができます。これにより、データのコピーによるオーバーヘッドを避けることができます。

1const worker = new Worker(new URL('./worker.ts', import.meta.url), { type: 'module' });
2
3const buffer = new ArrayBuffer(1024);
4worker.postMessage(buffer, [buffer]); // Transfer ownership to the Worker
5
6console.log(buffer.byteLength); // 0 (ownership moved)
  • worker.postMessage() の第2引数に [buffer] を渡すことで、buffer がコピーされるのではなく Worker に「移譲」されます。

  • この後、メインスレッド側の buffer は空(byteLengthが0)になり、Worker 側だけが使える状態になります。

Workerの終了

Workerは使い終わったらメモリの消費を抑えるために終了させる必要があります。terminateメソッドを使うことでWorkerを終了させることができます。

1const worker = new Worker(new URL('./worker.ts', import.meta.url), { type: 'module' });
2// ...
3
4worker.terminate(); // Terminate the Worker
  • このコードは、terminateメソッドを使ってWorkerを終了させる処理を行っています。

Workerの例外処理

Worker内でエラーが発生した場合、onerrorイベントを使ってエラーハンドリングができます。

1const worker = new Worker(new URL('./worker.ts', import.meta.url), { type: 'module' });
2
3worker.onerror = (error) => {
4    console.error("Error in Worker:", error.message);
5};
  • このコードは、Worker内で発生したエラーをonerrorイベントで捕捉して処理しています。

まとめ

TypeScriptでWorkerを使うことで、重い処理をバックグラウンドで実行しつつ、メインスレッドをスムーズに保つことが可能です。型定義を活用することで、メッセージのやり取りも型安全に行えます。データのやり取りやスレッドの管理に注意しつつ、パフォーマンスの向上や効率的な並行処理を実現できます。

YouTubeチャンネルでは、Visual Studio Codeを用いて上記の記事を見ながら確認できます。 ぜひYouTubeチャンネルもご覧ください。

YouTube Video