From 938c1c79d61232a3c9d82778d4728e5a48187aa9 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 28 Jun 2026 11:04:38 +0000 Subject: [PATCH] =?UTF-8?q?fix(=E6=B5=8B=E8=AF=95=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E7=94=9F=E6=88=90=E5=99=A8):=20=E4=BF=AE=E5=A4=8D=20Worker=20?= =?UTF-8?q?=E5=B9=B6=E5=8F=91=E7=94=9F=E6=88=90=E5=AF=BC=E8=87=B4=E7=9A=84?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E6=B1=A1=E6=9F=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: LingandRX --- .../hooks/__tests__/useGenerator.test.ts | 21 +++++++ .../TestDataGenerator/hooks/useGenerator.ts | 24 +++++--- src/workers/generator.worker.ts | 59 ++++++++++++------- 3 files changed, 75 insertions(+), 29 deletions(-) diff --git a/src/pages/TestDataGenerator/hooks/__tests__/useGenerator.test.ts b/src/pages/TestDataGenerator/hooks/__tests__/useGenerator.test.ts index 7ce233a..5d2ef73 100644 --- a/src/pages/TestDataGenerator/hooks/__tests__/useGenerator.test.ts +++ b/src/pages/TestDataGenerator/hooks/__tests__/useGenerator.test.ts @@ -114,4 +114,25 @@ describe('useGenerator', () => { expect(result.current.isGenerating).toBe(false); expect(worker.postedMessages).toEqual(expect.arrayContaining([{ type: 'cancel' }])); }); + + it('正在生成时不应重复发送 start 消息', () => { + const { result } = renderHook(() => useGenerator()); + + act(() => { + result.current.generate([mockField], 10); + result.current.generate([mockField], 20); + }); + + const worker = MockWorker.instances[0]; + const startMessages = worker.postedMessages.filter( + (message): message is { type: 'start'; payload: { count: number } } => + typeof message === 'object' && + message !== null && + 'type' in message && + message.type === 'start', + ); + + expect(startMessages).toHaveLength(1); + expect(startMessages[0]?.payload.count).toBe(10); + }); }); diff --git a/src/pages/TestDataGenerator/hooks/useGenerator.ts b/src/pages/TestDataGenerator/hooks/useGenerator.ts index e1d3221..2e5e1e7 100644 --- a/src/pages/TestDataGenerator/hooks/useGenerator.ts +++ b/src/pages/TestDataGenerator/hooks/useGenerator.ts @@ -37,6 +37,12 @@ export function useGenerator(): UseGeneratorReturn { const workerRef = useRef(null); const generationIdRef = useRef(0); + const isGeneratingRef = useRef(false); + + const finishGenerating = useCallback(() => { + isGeneratingRef.current = false; + setIsGenerating(false); + }, []); // 清理 Worker useEffect(() => { @@ -73,7 +79,7 @@ export function useGenerator(): UseGeneratorReturn { setProgress(data.payload); break; case 'complete': - setIsGenerating(false); + finishGenerating(); if (data.payload.success) { setResult(data.payload); } else if (data.payload.error && data.payload.error !== '生成已取消') { @@ -82,7 +88,7 @@ export function useGenerator(): UseGeneratorReturn { setProgress(null); break; case 'error': - setIsGenerating(false); + finishGenerating(); setError(data.payload.error); setProgress(null); break; @@ -91,7 +97,7 @@ export function useGenerator(): UseGeneratorReturn { worker.onerror = (err) => { console.error('[useGenerator] Worker 错误:', err); - setIsGenerating(false); + finishGenerating(); setError(err.message || 'Worker 运行错误'); setProgress(null); // Worker 出错后销毁,下次重新创建 @@ -101,16 +107,17 @@ export function useGenerator(): UseGeneratorReturn { workerRef.current = worker; return worker; - }, []); + }, [finishGenerating]); /** * 开始生成 */ const generate = useCallback( (fields: FieldConfig[], count: number, csvMode = false) => { - if (isGenerating) return; + if (isGeneratingRef.current) return; const generationId = ++generationIdRef.current; + isGeneratingRef.current = true; setIsGenerating(true); setProgress(null); @@ -124,21 +131,22 @@ export function useGenerator(): UseGeneratorReturn { }; worker.postMessage(message); }, - [isGenerating, getWorker], + [getWorker], ); /** * 取消生成 */ const cancel = useCallback(() => { - if (workerRef.current && isGenerating) { + if (workerRef.current && isGeneratingRef.current) { ++generationIdRef.current; const message: WorkerRequestMessage = { type: 'cancel' }; workerRef.current.postMessage(message); + isGeneratingRef.current = false; setIsGenerating(false); setProgress(null); } - }, [isGenerating]); + }, []); /** * 清除结果 diff --git a/src/workers/generator.worker.ts b/src/workers/generator.worker.ts index 2780b6f..1d1c7e4 100644 --- a/src/workers/generator.worker.ts +++ b/src/workers/generator.worker.ts @@ -14,10 +14,14 @@ import type { /** 每生成 N 行让出一次事件循环,以便处理 cancel 消息 */ const YIELD_EVERY = 100; -// 生成结果缓存 -let generatedData: Record[] = []; +/** 当前活跃生成任务 ID;新 start 会 supersede 旧任务 */ +let activeGenerationId: number | null = null; let isCancelled = false; +function shouldAbort(generationId: number): boolean { + return isCancelled || generationId !== activeGenerationId; +} + /** * Worker 消息处理器 */ @@ -27,12 +31,17 @@ self.onmessage = async (e: MessageEvent) => { switch (type) { case 'start': + activeGenerationId = data.payload.generationId; isCancelled = false; await handleStart(data.payload); break; case 'cancel': isCancelled = true; break; + default: { + const _exhaustive: never = type; + return _exhaustive; + } } }; @@ -46,7 +55,7 @@ async function handleStart(payload: { csvMode: boolean; }): Promise { const { generationId, fields, count } = payload; - generatedData = []; + const generatedData: Record[] = []; try { // 验证所有生成器是否存在 @@ -67,15 +76,17 @@ async function handleStart(payload: { // 生成数据 for (let i = 0; i < count; i++) { - if (isCancelled) { - self.postMessage({ - type: 'complete', - generationId, - payload: { - success: false, - error: '生成已取消', - }, - }); + if (shouldAbort(generationId)) { + if (generationId === activeGenerationId) { + self.postMessage({ + type: 'complete', + generationId, + payload: { + success: false, + error: '生成已取消', + }, + }); + } return; } @@ -148,20 +159,26 @@ async function handleStart(payload: { // 定期让出事件循环,使 cancel 消息能被处理 if ((i + 1) % YIELD_EVERY === 0) { await new Promise((resolve) => setTimeout(resolve, 0)); - if (isCancelled) { - self.postMessage({ - type: 'complete', - generationId, - payload: { - success: false, - error: '生成已取消', - }, - }); + if (shouldAbort(generationId)) { + if (generationId === activeGenerationId) { + self.postMessage({ + type: 'complete', + generationId, + payload: { + success: false, + error: '生成已取消', + }, + }); + } return; } } } + if (shouldAbort(generationId)) { + return; + } + const duration = Date.now() - startTime; const successCount = generatedData.filter((item) => Object.keys(item).length > 0).length;