with Web Workers
Introduction to Web Workers
Web Workers allow scripts to run in background threads, preventing the main thread from being blocked. TypeScript provides type support, making the use of Web Workers safer and more efficient. By offloading computationally intensive tasks to Workers, page responsiveness can be significantly improved.
Creating a Web Worker
Creating a Web Worker in TypeScript requires two files: the main thread script and the Worker script. First, create the Worker script:
// worker.ts
self.onmessage = (e: MessageEvent) => {
const data = e.data;
// Process data
const result = heavyCalculation(data);
self.postMessage(result);
};
function heavyCalculation(input: number[]): number {
return input.reduce((a, b) => a + b, 0);
}
Then, create a Worker instance in the main thread:
// main.ts
const worker = new Worker(new URL('./worker.ts', import.meta.url), {
type: 'module'
});
worker.onmessage = (e: MessageEvent) => {
console.log('Result from Worker:', e.data);
};
worker.postMessage([1, 2, 3, 4, 5]);
Type-Safe Worker Communication
To ensure type safety, communication interfaces can be defined:
// worker-types.ts
interface WorkerRequest {
type: 'calculate';
data: number[];
}
interface WorkerResponse {
type: 'result';
data: number;
}
Modify the Worker and main thread code to use these types:
// worker.ts
self.onmessage = (e: MessageEvent<WorkerRequest>) => {
if (e.data.type === 'calculate') {
const result = heavyCalculation(e.data.data);
self.postMessage({
type: 'result',
data: result
} as WorkerResponse);
}
};
Advanced Worker Patterns
Worker Pool
For scenarios requiring large-scale task processing, a Worker pool can be created:
class WorkerPool {
private workers: Worker[] = [];
private taskQueue: Array<{
task: WorkerRequest;
resolve: (value: WorkerResponse) => void;
}> = [];
constructor(size: number) {
for (let i = 0; i < size; i++) {
const worker = new Worker(new URL('./worker.ts', import.meta.url), {
type: 'module'
});
worker.onmessage = (e: MessageEvent<WorkerResponse>) => {
const nextTask = this.taskQueue.shift();
if (nextTask) {
worker.postMessage(nextTask.task);
nextTask.resolve(e.data);
}
};
this.workers.push(worker);
}
}
execute(task: WorkerRequest): Promise<WorkerResponse> {
return new Promise((resolve) => {
this.taskQueue.push({ task, resolve });
});
}
}
SharedArrayBuffer and Atomics
For scenarios requiring shared memory:
// Main thread
const sharedBuffer = new SharedArrayBuffer(1024);
const sharedArray = new Int32Array(sharedBuffer);
const worker = new Worker(new URL('./shared-worker.ts', import.meta.url));
worker.postMessage({ buffer: sharedBuffer });
// shared-worker.ts
self.onmessage = (e: MessageEvent<{ buffer: SharedArrayBuffer }>) => {
const sharedArray = new Int32Array(e.data.buffer);
Atomics.add(sharedArray, 0, 1);
self.postMessage({ done: true });
};
Error Handling in Workers
Properly handle errors in Workers:
// worker.ts
try {
// Code that might fail
self.postMessage({ result: heavyCalculation(data) });
} catch (error) {
self.postMessage({
error: true,
message: error instanceof Error ? error.message : 'Unknown error'
});
}
// Main thread
worker.onmessage = (e: MessageEvent<{ result?: number; error?: boolean; message?: string }>) => {
if (e.data.error) {
console.error('Worker error:', e.data.message);
} else {
console.log('Result:', e.data.result);
}
};
worker.onerror = (e: ErrorEvent) => {
console.error('Worker runtime error:', e.message);
};
Modularizing Workers
Organize Worker code using ES modules:
// math-utils.ts
export function calculateSum(numbers: number[]): number {
return numbers.reduce((a, b) => a + b, 0);
}
// worker.ts
import { calculateSum } from './math-utils';
self.onmessage = (e: MessageEvent<number[]>) => {
const result = calculateSum(e.data);
self.postMessage(result);
};
Worker Lifecycle Management
Properly manage Worker lifecycle:
class TaskProcessor {
private worker: Worker | null = null;
start() {
if (!this.worker) {
this.worker = new Worker(new URL('./worker.ts', import.meta.url));
this.worker.onmessage = this.handleMessage;
}
}
stop() {
if (this.worker) {
this.worker.terminate();
this.worker = null;
}
}
private handleMessage = (e: MessageEvent) => {
// Handle messages
};
postTask(data: unknown) {
if (this.worker) {
this.worker.postMessage(data);
}
}
}
Limitations of Web Workers
Understanding Worker limitations is important:
- Cannot directly access the DOM
- Cannot use certain APIs (e.g., localStorage)
- Communication data is copied via the structured clone algorithm
Performance Optimization Tips
Methods to optimize Worker performance:
- Batch process messages
- Use Transferable objects
- Set an appropriate number of Workers
// Using Transferable objects
const largeBuffer = new ArrayBuffer(10000000);
worker.postMessage(largeBuffer, [largeBuffer]);
// Batch processing
worker.postMessage({
tasks: [task1, task2, task3],
batchId: 'batch-123'
});
Coordination Between Workers and Main Thread
Implement complex coordination logic:
// Main thread
const worker = new Worker(new URL('./coordinated-worker.ts', import.meta.url));
worker.onmessage = (e: MessageEvent<{ type: string; data: unknown }>) => {
switch (e.data.type) {
case 'progress':
updateProgressBar(e.data.data as number);
break;
case 'result':
displayResult(e.data.data);
break;
case 'error':
showError(e.data.data as string);
break;
}
};
// coordinated-worker.ts
function processInChunks(data: LargeDataSet, chunkSize: number) {
for (let i = 0; i < data.length; i += chunkSize) {
const chunk = data.slice(i, i + chunkSize);
const result = processChunk(chunk);
self.postMessage({
type: 'progress',
data: (i + chunkSize) / data.length
});
self.postMessage({
type: 'partial-result',
data: result
});
}
}
Advanced TypeScript Types in Workers
Leverage TypeScript's advanced type features:
// Define message type mappings
type WorkerMessages = {
calculate: { numbers: number[] };
fetch: { url: string };
cancel: null;
};
type WorkerResponses = {
result: { value: number };
data: { content: string };
error: { message: string };
};
// Type-safe Worker wrapper
class TypedWorker {
private worker: Worker;
constructor() {
this.worker = new Worker(new URL('./typed-worker.ts', import.meta.url));
}
postMessage<T extends keyof WorkerMessages>(
type: T,
data: WorkerMessages[T]
): Promise<WorkerResponses[keyof WorkerResponses]> {
return new Promise((resolve, reject) => {
const messageId = Math.random().toString(36).slice(2);
const handler = (e: MessageEvent<{
messageId: string;
type: keyof WorkerResponses;
data: WorkerResponses[keyof WorkerResponses];
}>) => {
if (e.data.messageId === messageId) {
this.worker.removeEventListener('message', handler);
if (e.data.type === 'error') {
reject(e.data.data);
} else {
resolve(e.data.data);
}
}
};
this.worker.addEventListener('message', handler);
this.worker.postMessage({ type, data, messageId });
});
}
}
State Management in Workers
Manage complex state in Workers:
// state-worker.ts
interface State {
counter: number;
data: Record<string, unknown>;
processing: boolean;
}
let state: State = {
counter: 0,
data: {},
processing: false
};
self.onmessage = (e: MessageEvent<{ type: string; payload?: unknown }>) => {
switch (e.data.type) {
case 'increment':
state.counter++;
self.postMessage({ type: 'state-update', state });
break;
case 'load-data':
state.processing = true;
loadDataAsync(e.data.payload as string).then(data => {
state.data = data;
state.processing = false;
self.postMessage({ type: 'data-loaded', data });
});
break;
case 'get-state':
self.postMessage({ type: 'current-state', state });
break;
}
};
async function loadDataAsync(url: string): Promise<Record<string, unknown>> {
// Simulate async data loading
return new Promise(resolve => {
setTimeout(() => resolve({ url, timestamp: Date.now() }), 1000);
});
}
Integrating Web Workers in Modern Frameworks
Example of using Workers in React:
// useWorker.ts
import { useEffect, useRef, useState } from 'react';
export function useWorker<T, R>(workerUrl: string) {
const [result, setResult] = useState<R | null>(null);
const [error, setError] = useState<Error | null>(null);
const workerRef = useRef<Worker | null>(null);
useEffect(() => {
const worker = new Worker(new URL(workerUrl, import.meta.url));
workerRef.current = worker;
worker.onmessage = (e: MessageEvent<R>) => {
setResult(e.data);
};
worker.onerror = (e) => {
setError(new Error(e.message));
};
return () => {
worker.terminate();
};
}, [workerUrl]);
const postMessage = (data: T) => {
if (workerRef.current) {
workerRef.current.postMessage(data);
}
};
return { result, error, postMessage };
}
// Usage in component
function CalculationComponent() {
const { result, error, postMessage } = useWorker<number[], number>('./worker.ts');
const handleCalculate = () => {
postMessage([1, 2, 3, 4, 5]);
};
return (
<div>
<button onClick={handleCalculate}>Calculate</button>
{error && <div>Error: {error.message}</div>}
{result !== null && <div>Result: {result}</div>}
</div>
);
}
Complex Calculation Example in Workers
Implementing an image processing Worker:
// image-worker.ts
interface ImageTask {
id: string;
imageData: ImageData;
operations: ('grayscale' | 'invert' | 'blur')[];
}
self.onmessage = (e: MessageEvent<ImageTask>) => {
const { id, imageData, operations } = e.data;
const result = processImage(imageData, operations);
self.postMessage({
id,
result
}, [result.data.buffer]); // Transfer image data using Transferable
};
function processImage(imageData: ImageData, operations: string[]): ImageData {
const { width, height, data } = imageData;
const result = new ImageData(width, height);
const resultData = result.data;
// Copy original data
for (let i = 0; i < data.length; i++) {
resultData[i] = data[i];
}
// Apply each operation
operations.forEach(op => {
switch (op) {
case 'grayscale':
applyGrayscale(resultData);
break;
case 'invert':
applyInvert(resultData);
break;
case 'blur':
applyBlur(resultData, width, height);
break;
}
});
return result;
}
function applyGrayscale(data: Uint8ClampedArray) {
for (let i = 0; i < data.length; i += 4) {
const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
data[i] = data[i + 1] = data[i + 2] = avg;
}
}
// Other image processing functions...
Performance Considerations for Worker Communication
Measuring Worker communication overhead:
// Performance test Worker
self.onmessage = async (e: MessageEvent<{ type: string; size: number }>) => {
if (e.data.type === 'benchmark') {
const { size } = e.data;
// Test small messages
const smallStart = performance.now();
for (let i = 0; i < 1000; i++) {
self.postMessage({ type: 'small', data: i });
}
const smallEnd = performance.now();
// Test large messages
const largeData = new ArrayBuffer(size);
const largeStart = performance.now();
for (let i = 0; i < 100; i++) {
self.postMessage({ type: 'large', data: largeData }, [largeData]);
}
const largeEnd = performance.now();
self.postMessage({
type: 'benchmark-result',
smallMessageTime: (smallEnd - smallStart) / 1000,
largeMessageTime: (largeEnd - largeStart) / 100,
transferrableRatio: size / (largeEnd - largeStart)
});
}
};
Parallel Computing Patterns in Workers
Implementing parallel task decomposition:
// parallel-worker.ts
interface Task {
id: string;
start: number;
end: number;
data: Float64Array;
}
interface Result {
id: string;
value: number;
}
self.onmessage = (e: MessageEvent<Task>) => {
const { id, start, end, data } = e.data;
let sum = 0;
// Compute assigned portion
for (let i = start; i < end; i++) {
sum += data[i];
}
const result: Result = {
id,
value: sum
};
self.postMessage(result);
};
// Usage in main thread
async function parallelSum(data: Float64Array, chunkCount: number): Promise<number> {
const chunkSize = Math.ceil(data.length / chunkCount);
const workers: Worker[] = [];
const promises: Promise<number>[] = [];
for (let i = 0; i < chunkCount; i++) {
const start = i * chunkSize;
const end = Math.min(start + chunkSize, data.length);
const worker = new Worker(new URL('./parallel-worker.ts', import.meta.url));
const promise = new Promise<number>((resolve) => {
worker.onmessage = (e: MessageEvent<Result>) => {
resolve(e.data.value);
worker.terminate();
};
});
workers.push(worker);
promises.push(promise);
// Send data copy to Worker
const chunk = new Float64Array(data.buffer.slice(
start * Float64Array.BYTES_PER_ELEMENT,
end * Float64Array.BYTES_PER_ELEMENT
));
worker.postMessage({
id: `chunk-${i}`,
start: 0,
end: chunk.length,
data: chunk
}, [chunk.buffer]);
}
const results = await Promise.all(promises);
return results.reduce((a, b) => a + b, 0);
}
Caching Strategies in Workers
Implementing data caching in Workers:
// caching-worker.ts
interface CacheItem {
timestamp: number;
data: unknown;
ttl: number;
}
const cache = new Map<string, CacheItem>();
const MAX_CACHE_SIZE = 100;
function getFromCache(key: string): unknown | null {
const item = cache.get(key);
if (!item) return null;
// Check if expired
if (Date.now() - item.timestamp > item.ttl) {
cache.delete(key);
return null;
}
return item.data;
}
function setToCache(key: string, data: unknown, ttl: number = 60000) {
if (cache.size >= MAX_CACHE_SIZE) {
// Simple LRU strategy
const oldestKey = cache.keys().next().value;
cache.delete(oldestKey);
}
cache.set(key, {
timestamp: Date.now(),
data,
ttl
});
}
self.onmessage = async (e: MessageEvent<{
type: 'get' | 'set';
key: string;
data?: unknown;
ttl?: number
}>) => {
const { type, key, data, ttl } = e.data;
if (type === 'get') {
const cached = getFromCache(key);
self.postMessage({
type: 'cache-response',
key,
data: cached,
hit: cached !== null
});
} else if (type === 'set' && data !== undefined) {
setToCache(key, data, ttl);
self.postMessage({
type: 'cache-set',
key,
success: true
});
}
};
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:与GraphQL配合
下一篇:与WebAssembly