以下內容基於提供文章所描述的真實情境,將同一個實戰主題拆解為 15 個具教學價值的「問題解決案例」。每個案例都包含問題、根因、解法、關鍵程式碼、成效指標與練習與評估,用於專案演練與能力評估。
Case #1: 單一 Process 多執行緒無法提升 CR2->JPG 效率
Problem Statement(問題陳述)
業務場景:在 Windows 上的 .NET/WPF 歸檔工具需要大量將 Canon .CR2 RAW 檔轉成 .JPG。系統原本以 ThreadPool 平行處理各種可並行的小工作以提高 CPU 使用率,但整體效能仍卡在 CR2->JPG 轉檔步驟。批次常動輒上百張,單張約 70 秒,導致整體處理時間過長,影響歸檔與後續工作流程的效率與 SLA。
技術挑戰:即使使用 ThreadPool 增加並行工作數,轉檔步驟仍無法真正平行,CPU 使用率無法接近 100%。
影響範圍:整體批次吞吐量、CPU 利用率、作業完成時間、使用者等待體驗。
複雜度評級:中
Root Cause Analysis(根因分析)
直接原因:
- Canon Codec 在同一個 process 內疑似不可重入(non-reentrant),造成同一時間只能一個執行緒進入轉檔。
- ThreadPool 雖能讓其他小任務並行,但轉檔為長耗時主瓶頸,其他任務很快做完,無法掩蓋主瓶頸。
- 單一 process 內部的鎖定/資源限制讓多執行緒無法對轉檔真正並行。
深層原因:
- 架構層面:將長耗時轉檔工作與其他任務共置於單一 process,無法突破單一 process 內部的鎖與資源限制。
- 技術層面:對第三方 Codec 的可重入性與併發行為缺乏驗證與隔離策略。
- 流程層面:性能驗證偏重執行緒併發,而未考量 process 隔離層級的實驗與設計。
Solution Design(解決方案設計)
解決策略:先建立性能假設與驗證路徑:假設 Canon Codec 的限制只在單一 process 內,透過同時啟動兩個獨立 process 來並行轉檔,藉由 process 隔離避開不可重入限制。先用小型實驗程式同時跑兩份轉檔來觀察 CPU 使用率與吞吐量,再將轉檔抽離為獨立 exe,主程式僅負責啟動與監控兩個工作 process。
實施步驟:
- 建立最小實驗
- 實作細節:快速寫一個只做 CR2->JPG 的小 exe,同時手動啟動兩份。
- 所需資源:.NET、Canon Codec、測試 RAW 檔。
- 預估時間:0.5 天。
- 量測與分析
- 實作細節:觀察 CPU 使用率、單張時間、是否互相拖慢。
- 所需資源:任一系統監控(工作管理員/Performance Monitor)。
- 預估時間:0.5 天。
關鍵程式碼/設定:
// 示意:單一 process 多執行緒嘗試(無效的作法示例,凸顯問題)
Parallel.ForEach(files, new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }, f =>
{
// 假設 CanonCodec.Convert 在同一 process 不可重入
CanonCodec.Convert(f, GetOutputPath(f)); // 實測會序列化或拋出錯誤,CPU 利用率上不去
});
實際案例:文章中先用簡單轉檔執行檔,手動同時 RUN 兩份觀察 CPU 飆到約 80%,每份執行速度未下降。
實作環境:.NET/WPF、Windows、Canon Codec、CPU: Intel E6300(雙核)
實測數據:
- 改善前:CPU 利用率低(顯著低於 80%);吞吐量約 1 張/70 秒
- 改善後:CPU 約 80%;雖然單張仍約 70 秒,但 70 秒可完成 2 張
- 改善幅度:吞吐量約 2 倍
Learning Points(學習要點) 核心知識點:
- 第三方庫的可重入特性會限制單一 process 多執行緒的效益
- 多執行緒與多 process 的選型考量
- 先行小型實驗驗證性能假設
技能要求:
- 必備技能:C#/.NET 基礎、基本性能量測
- 進階技能:對外部庫併發特性的鑑別能力
延伸思考:
- 還有哪些庫存在 process 級別的限制?
- 如何在設計早期納入可重入性驗證?
- 何時該選擇 process 隔離而非執行緒併發?
Practice Exercise(練習題)
- 基礎練習:寫一個簡單轉檔 stub,模擬 70 秒延遲,並嘗試 Parallel.ForEach(30 分)
- 進階練習:量測 CPU 使用率與多執行緒對 stub 的影響(2 小時)
- 專案練習:做出一個最小可行轉檔工具,手動啟動兩份驗證吞吐量(8 小時)
Assessment Criteria(評估標準)
- 功能完整性(40%):能重現單一 process 併發無效的結果
- 程式碼品質(30%):可讀性、日誌與錯誤處理
- 效能優化(20%):有量測數據支撐結論
- 創新性(10%):提出合理驗證變體(如不同 MaxDegreeOfParallelism) ```
Case #2: 確認 Canon Codec 在單一 Process 的不可重入限制
Problem Statement(問題陳述)
業務場景:歸檔系統的轉檔步驟使用 Canon Codec。嘗試在同一 process 提升並發後仍無效,推測原因與 Codec 的不可重入性有關。需要實證這個限制,為後續架構調整提供依據。
技術挑戰:外部閉源 Codec 的內部機制不可見,僅能黑箱驗證。
影響範圍:技術選型、架構決策(執行緒 vs. process)、後續維護與擴展。
複雜度評級:中
Root Cause Analysis(根因分析)
直接原因:
- Canon Codec 在單一 process 內不能同時重入,多執行緒呼叫被序列化或被鎖。
- 共享狀態(靜態資源或全域物件)導致 reentrancy 失敗。
- 缺乏官方可重入保證,導致誤用。
深層原因:
- 架構層面:過度假設第三方庫可與 ThreadPool 自然相容。
- 技術層面:未針對黑箱組件進行壓力與併發行為測試。
- 流程層面:缺乏「假設-實驗-結論」的性能驗證流程。
Solution Design(解決方案設計)
解決策略:以黑箱測試設計驗證不可重入性。建立兩種測試:同一 process 多執行緒 vs. 兩個獨立 process 同步執行,對比 CPU 使用率與吞吐量。若後者顯著提升,則基本可判定不可重入限制在 process 範圍內。
實施步驟:
- 同一 process 壓測
- 實作細節:ThreadPool/Parallel.ForEach 壓測,記錄 CPU/時間。
- 所需資源:.NET、性能監控工具。
- 預估時間:0.5 天。
- 兩 process 壓測
- 實作細節:同檔案集同時啟動兩個 exe,觀察 CPU/吞吐。
- 所需資源:兩個轉檔 exe 實例。
- 預估時間:0.5 天。
關鍵程式碼/設定:
// 同一 process vs 兩 process 的黑箱驗證骨架
var sw = Stopwatch.StartNew();
RunInSingleProcessMultiThread(files); // 預期 CPU 利用率上不去
sw.Stop();
Console.WriteLine($"Single-process MT: {sw.Elapsed}");
sw.Restart();
RunInTwoProcesses(files); // 預期 CPU ~80%,吞吐增長
sw.Stop();
Console.WriteLine($"Two-process: {sw.Elapsed}");
實際案例:文章中兩個獨立 process 同時執行時,CPU 升至 ~80%,且單份速度不降,支持「不可重入限制在單一 process」的判斷。
實作環境:同 Case #1
實測數據:
- 改善前:單一 process 併發無效
- 改善後:兩 process 並行達成 2x 吞吐
- 改善幅度:吞吐約翻倍
Learning Points(學習要點)
- 黑箱元件的可重入性驗證方法
- 用對照組確立架構決策
- 以實測取代假設
技能要求:
- 必備技能:壓測、Stopwatch、效能監控
- 進階技能:設計可重現的對照實驗
延伸思考:
- 若不可重入是跨 process 的,還能怎麼辦?
- 是否能以容器/沙箱提供更強隔離?
- 何時選用替代 Codec?
Practice Exercise
- 基礎:設計單/雙 process 的轉檔 stub 對照測試(30 分)
- 進階:自動化產出報表(2 小時)
- 專案:將測試納入 CI,防止回歸(8 小時)
Assessment Criteria
- 功能完整性:對照實驗可重現
- 程式碼品質:清晰實驗框架
- 效能優化:正確量測與分析
- 創新性:提出補充驗證(如不同檔案大小) ```
Case #3: 建立即時驗證的雙 Process 轉檔 PoC
Problem Statement(問題陳述)
業務場景:在全面改造前,需以最小成本驗證「兩個獨立 process 可同時使用兩顆 CPU 做轉檔」的假設,降低重構風險。
技術挑戰:快速構建 PoC,並量測 CPU 與吞吐量,不追求完美,只求驗證。
影響範圍:是否投入抽離轉檔為獨立 exe 的工程量。
複雜度評級:低
Root Cause Analysis(根因分析)
直接原因:
- 缺乏數據支持的決策會導致不必要的重構。
- 需要驗證 process 隔離是否能避開不可重入。
- 需要評估同時執行兩份是否造成單份速度下降。
深層原因:
- 架構層面:缺乏 MVP/PoC 驅動的決策流程。
- 技術層面:性能決策未有標準化量測。
- 流程層面:先上再改 vs. 先驗證再落地的差異。
Solution Design(解決方案設計)
解決策略:以現有 LIB 快速寫一個僅做 CR2->JPG 的小 exe,同時啟動兩份執行。用系統監視器觀察 CPU,並記錄兩份完成時間與單份耗時是否變差。
實施步驟:
- 開發最小轉檔 exe
- 實作細節:主程式只接 input/output,呼叫編碼 API。
- 所需資源:.NET、Canon Codec。
- 預估時間:0.5 天。
- 同時啟動兩份並量測
- 實作細節:手動執行兩份,或用批次檔同時啟動。
- 所需資源:工作管理員/PerfMon。
- 預估時間:0.5 天。
關鍵程式碼/設定:
:: 簡單批次檔,同時啟動兩份
start Converter.exe "A.CR2" "A.JPG"
start Converter.exe "B.CR2" "B.JPG"
實際案例:文章作者同時跑兩份,CPU ~80%,單份速度不降;確認可行後才投入重構。
實作環境:同 Case #1
實測數據:
- 改善前:單份 70 秒;整體 1 張/70 秒
- 改善後:兩份同時 70 秒完成兩張
- 改善幅度:吞吐約 2 倍
Learning Points
- 用 PoC 迅速降低決策風險
- 度量結果比主觀判斷可靠
- 小步快跑的工程實踐
技能要求:
- 必備:批次與命令列運行、簡單量測
- 進階:設計可重現的實驗腳本
延伸思考:
- 如何將 PoC 轉為長期架構的一部分?
- PoC 後如何設計清晰的淘汰策略?
Practice Exercise
- 基礎:建立 Converter.exe 範例(30 分)
- 進階:用批次/PowerShell 同啟兩份並記錄時間(2 小時)
- 專案:製作簡單報表比較單/雙 process(8 小時)
Assessment Criteria
- 功能完整性:PoC 可穩定運行
- 程式碼品質:清楚簡潔
- 效能優化:數據支撐結論
- 創新性:自動生成比較報表 ```
Case #4: 以 Orchestrator-Worker 架構抽離轉檔為獨立 .exe
Problem Statement(問題陳述)
業務場景:將原本內嵌於歸檔程式的轉檔邏輯抽離為 Converter.exe,主程式作為 Orchestrator 控制兩個 worker 並行,以達到長期穩定的 2x 吞吐。
技術挑戰:設計簡單可靠的主從式流程,控制並行數、監控與錯誤回傳。
影響範圍:系統結構清晰度、部署與維護、可擴充性。
複雜度評級:中
Root Cause Analysis
直接原因:
- 單一 process 內併發受限,需要 process 級別隔離。
- 內嵌轉檔導致耦合度高、不利於獨立擴展與監控。
- 需要有穩定的調度機制維持兩個轉檔實例同時工作。
深層原因:
- 架構層面:缺乏明確的 Orchestrator-Worker 分層。
- 技術層面:未建立跨 process 的基礎通訊約定(參數與回傳值)。
- 流程層面:缺少對長任務的專屬管線管理。
Solution Design
解決策略:將轉檔邏輯封裝為 Converter.exe,Orchestrator 啟動兩個 child process 並維持工作隊列,使用命令列參數傳遞輸入/輸出路徑,以 exit code 傳遞結果。持續餵任務直到完成。
實施步驟:
- 封裝轉檔為 Converter.exe
- 實作細節:Main 解析參數,呼叫 Canon Codec,回傳 exit code。
- 所需資源:.NET、Canon Codec。
- 預估時間:1 天。
- Orchestrator 控制兩 worker
- 實作細節:ProcessStartInfo、並行度=2、等待/重試/日誌。
- 所需資源:.NET。
- 預估時間:1 天。
關鍵程式碼/設定:
// Orchestrator 以兩個並行的外部進程處理任務
BlockingCollection<(string input, string output)> queue = new(...);
var workers = Enumerable.Range(0, 2).Select(_ => Task.Run(async () =>
{
foreach (var job in queue.GetConsumingEnumerable())
{
var psi = new ProcessStartInfo("Converter.exe", $"\"{job.input}\" \"{job.output}\"")
{
UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true
};
using var p = Process.Start(psi);
await p!.WaitForExitAsync();
if (p.ExitCode != 0) { /* 記錄失敗並重試或標記 */ }
}
})).ToArray();
Task.WaitAll(workers);
實際案例:文章中抽離為 .exe,且維持同時兩個轉檔程序,達到 2x 吞吐。
實作環境:同 Case #1
實測數據:
- 改善前:1 張/70 秒
- 改善後:70 秒完成 2 張
- 改善幅度:吞吐約 2 倍;CPU 約 80%
Learning Points
- Process 隔離繞過不可重入限制
- Orchestrator-Worker 模式的最小落地實作
- 命令列與 exit code 做為輕量 IPC
技能要求:
- 必備:Process API、命令列參數處理
- 進階:可靠的併發控制與錯誤處理
延伸思考:
- 如何擴展到 N 個 worker?
- 如何避免磁碟/IO 變成新瓶頸?
Practice Exercise
- 基礎:撰寫 Converter.exe(30 分)
- 進階:寫出 2 worker 的 Orchestrator 與重試(2 小時)
- 專案:將 Orchestrator 併入現有 WPF 工具並有進度條(8 小時)
Assessment Criteria
- 功能完整性:可穩定併發轉檔
- 程式碼品質:模組化、錯誤與日誌
- 效能優化:維持 CPU 高利用率
- 創新性:可配置的並行度 ```
Case #5: 以命令列參數 + ExitCode 的極簡 IPC 設計
Problem Statement(問題陳述)
業務場景:轉檔抽離為外部 exe 後,需要簡單可維護的跨 process 通訊。複雜 IPC(Named Pipes、WCF)會拉高成本且非運算瓶頸。
技術挑戰:在盡量不增加複雜度的前提下,可靠地傳遞輸入/輸出與回傳狀態。
影響範圍:穩定性、維護成本、開發速度。
複雜度評級:低
Root Cause Analysis
直接原因:
- IPC 太複雜會拖慢落地進度。
- 啟動參數與回傳值解析若不謹慎會出錯。
- 轉檔耗時 70 秒,IPC 的毫秒級開銷無足輕重。
深層原因:
- 架構層面:過度工程化風險。
- 技術層面:忽略簡單可靠解法(arguments + exit code)。
- 流程層面:性價比評估不明確。
Solution Design
解決策略:使用命令列參數傳遞 input/output 路徑,使用 ExitCode 表示成功/失敗,必要時以標準輸出輸出錯誤訊息。因 IPC 開銷極小,對 70 秒任務無可感延遲。
實施步驟:
- 定義參數與 ExitCode 協議
- 實作細節:0=成功,非 0=失敗;參數順序固定。
- 所需資源:無特別需求。
- 預估時間:0.25 天。
- Orchestrator 實作解析與重試
- 實作細節:引號處理、空白路徑、ExitCode 判斷。
- 所需資源:.NET。
- 預估時間:0.5 天。
關鍵程式碼/設定:
// Worker: Converter.exe
static int Main(string[] args)
{
if (args.Length < 2) return 2; // 參數錯誤
var input = args[0];
var output = args[1];
try
{
CanonCodec.Convert(input, output);
return 0;
}
catch (Exception ex)
{
Console.Error.WriteLine(ex);
return 1; // 轉檔失敗
}
}
實際案例:作者評估 IPC 的麻煩度與成本後,仍採用 arguments/exit code,最終達到 2 倍吞吐(證明 IPC 成本可接受)。
實作環境:同 Case #1
實測數據:
- 改善前:單 process 吞吐 1x
- 改善後:雙 process 吞吐 2x(IPC 開銷無感)
- 改善幅度:吞吐約翻倍;CPU 約 80%
Learning Points
- 輕量 IPC 優先:arguments + exit code
- 以業務成本觀念衡量工程複雜度
- 可靠性優先於過度抽象
技能要求:
- 必備:命令列處理、ExitCode
- 進階:標準輸出/錯誤串流處理
延伸思考:
- 何時需要升級為 NamedPipe/WCF?
- 如何為 exit code 制定可維護的表?
Practice Exercise
- 基礎:解析兩個參數並回傳 ExitCode(30 分)
- 進階:標準錯誤輸出與 Orchestrator 重試(2 小時)
- 專案:加上 simple JSON status 檔輸出(8 小時)
Assessment Criteria
- 功能完整性:正確參數/回傳
- 程式碼品質:健壯的解析
- 效能優化:無不必要阻塞
- 創新性:可擴展的狀態碼設計 ```
Case #6: 兩個 Worker 並行的任務調度與併發維持
Problem Statement(問題陳述)
業務場景:非轉檔類的小任務很快完成,整體效能卡在轉檔。需要保證在整個批次期間,始終有兩個轉檔在進行,以維持 CPU 利用率與吞吐。
技術挑戰:防止 worker 閒置、確保隊列飢餓問題不發生。
影響範圍:吞吐量與整體耗時。
複雜度評級:中
Root Cause Analysis
直接原因:
- 其他任務太快完成,不能掩蓋主瓶頸。
- 若不持續餵任務,CPU 利用率會掉。
- 缺乏簡單「持續兩個並行」的調度器。
深層原因:
- 架構層面:未拆分「主瓶頸專屬」管線。
- 技術層面:未建立 blocking queue + 固定併發度。
- 流程層面:批次任務缺乏併發維持策略。
Solution Design
解決策略:使用 BlockingCollection(或簡單 Queue+Lock),建兩個 worker 任務從隊列取檔轉檔直到完成,確保在任何時刻最多並行兩個轉檔。
實施步驟:
- 準備任務隊列
- 實作細節:預先產生所有 input/output 對,入隊。
- 所需資源:.NET。
- 預估時間:0.5 天。
- 啟動兩個 worker
- 實作細節:Task.Run 兩個消費者,直到隊列完成。
- 所需資源:.NET。
- 預估時間:0.5 天。
關鍵程式碼/設定:
using var queue = new BlockingCollection<Job>(boundedCapacity: 1024);
var workers = new List<Task>();
for (int i = 0; i < 2; i++)
{
workers.Add(Task.Run(() =>
{
foreach (var job in queue.GetConsumingEnumerable())
{
RunConverter(job); // 啟動外部 exe
}
}));
}
// 入隊後 CompleteAdding
// 等待 workers 結束
實際案例:作者最終維持兩個轉檔程序並行,達到 2x 吞吐。
實作環境:同 Case #1
實測數據:
- 改善前:1 張/70 秒
- 改善後:2 張/70 秒,CPU ~80%
- 改善幅度:吞吐 2 倍
Learning Points
- 固定併發度與隊列消費模型
- 針對主瓶頸建立專屬管線
- 防止 worker 閒置策略
技能要求:
- 必備:並行與併發控制
- 進階:阻塞隊列/背壓
延伸思考:
- 如何自動調整併發度?
- 如何處理超大批次的流量尖峰?
Practice Exercise
- 基礎:BlockingCollection 兩 worker 範例(30 分)
- 進階:加上重試與 backoff(2 小時)
- 專案:加入進度與取消支援(8 小時)
Assessment Criteria
- 功能完整性:維持固定併發
- 程式碼品質:清晰的隊列與工作者
- 效能優化:無閒置、吞吐穩定
- 創新性:可配置與統計 ```
Case #7: 驗證「同時跑兩份不會變慢」的實證量測
Problem Statement(問題陳述)
業務場景:擔心同時執行兩份轉檔會互相拖慢,抵消併發效果。需要實證單份耗時是否維持在 70 秒左右。
技術挑戰:一致的量測方法與結果解讀。
影響範圍:是否採納雙 process 架構。
複雜度評級:低
Root Cause Analysis
直接原因:
- CPU、I/O 爭用可能導致單份速度下降。
- 未量測容易做出不實際預估。
- 需消除「兩份會變慢」的疑慮。
深層原因:
- 架構層面:決策需有數據依據。
- 技術層面:量測設計需考慮背景噪音。
- 流程層面:建立性能驗收標準。
Solution Design
解決策略:以固定資料集,在空閒系統中各跑單份/雙份測試數次取平均。以 Stopwatch 記錄單份完成時間,並觀察是否接近 70 秒。
實施步驟:
- 單份基線測試
- 實作細節:單 process 單任務;取 N 次平均。
- 所需資源:Stopwatch。
- 預估時間:0.5 天。
- 雙份併發測試
- 實作細節:同時啟兩份,紀錄各自耗時。
- 所需資源:Stopwatch、批次啟動。
- 預估時間:0.5 天。
關鍵程式碼/設定:
var sw = Stopwatch.StartNew();
RunConverter("A.CR2","A.JPG"); // 單份
sw.Stop();
Console.WriteLine($"Single run: {sw.Elapsed}");
Parallel.Invoke(
() => { var s=Stopwatch.StartNew(); RunConverter("B.CR2","B.JPG"); Console.WriteLine($"B:{s.Elapsed}"); },
() => { var s=Stopwatch.StartNew(); RunConverter("C.CR2","C.JPG"); Console.WriteLine($"C:{s.Elapsed}"); }
);
實際案例:文章指出同時跑兩份「執行速度倒沒有下降,差不多」。
實作環境:同 Case #1
實測數據:
- 改善前:單份 70 秒
- 改善後:雙份各約 70 秒,總吞吐 2x
- 改善幅度:吞吐翻倍;單份速度不降
Learning Points
- 以基線對照判斷併發效益
- 單任務與多任務同時的比較方法
- 避免背景負載干擾測試
技能要求:
- 必備:Stopwatch、簡單壓測
- 進階:統計平均/中位/離群值
延伸思考:
- 何時應綁定 CPU affinity 測試?
- 什麼情況會導致單份變慢?
Practice Exercise
- 基礎:完成單/雙份量測(30 分)
- 進階:作 10 次平均並報表(2 小時)
- 專案:建立自動化壓測腳本(8 小時)
Assessment Criteria
- 功能完整性:量測可重現
- 程式碼品質:清楚紀錄
- 效能優化:分析合理
- 創新性:加入誤差範圍與圖表 ```
Case #8: 實作最小風險的轉檔 Worker 與錯誤回傳
Problem Statement(問題陳述)
業務場景:Worker exe 必須穩定處理單張轉檔,確保錯誤能透過 ExitCode 傳回 Orchestrator。
技術挑戰:參數解析、異常處理、狀態回傳需簡潔可靠。
影響範圍:整體批次穩定度與重試策略。
複雜度評級:低
Root Cause Analysis
直接原因:
- 跨 process 需要以 ExitCode 傳遞結果。
- 解析錯誤或未捕捉例外會導致不明失敗。
- 需要標準化錯誤處理。
深層原因:
- 架構層面:明確 contract(參數/回傳)是跨 process 關鍵。
- 技術層面:健壯的邊界條件處理。
- 流程層面:失敗與重試策略設計。
Solution Design
解決策略:Worker 嚴格驗證參數、try-catch 包覆轉檔、寫 stderr 以利診斷、以 ExitCode 報告結果。避免多餘狀態,簡化維護。
實施步驟:
- 定義 ExitCode 與訊息格式
- 實作細節:0/1/2… 定義;stderr 詳述原因。
- 所需資源:無。
- 預估時間:0.25 天。
- 編碼與測試邊界
- 實作細節:空路徑、權限不足等模擬。
- 所需資源:測試檔案與目錄。
- 預估時間:0.5 天。
關鍵程式碼/設定:
try
{
// 核心轉檔
CanonCodec.Convert(input, output);
Console.WriteLine("OK");
return 0;
}
catch (IOException io)
{
Console.Error.WriteLine($"IO_ERROR:{io.Message}");
return 3;
}
catch (Exception ex)
{
Console.Error.WriteLine($"GENERAL:{ex}");
return 1;
}
實際案例:文章採用簡單回傳值策略且實作成功,最終吞吐達 2x,證明設計可行。
實作環境:同 Case #1
實測數據:
- 改善前:無法跨 process 傳遞結果
- 改善後:ExitCode 標準化,整體吞吐 2x
- 改善幅度:穩定性與可觀測性提升(支撐 2x 吞吐)
Learning Points
- Worker 最小面積原則
- ExitCode 與 stderr 的協同
- 錯誤分類與重試依據
技能要求:
- 必備:例外處理、流程控制
- 進階:錯誤碼設計
延伸思考:
- 是否需要結構化日誌(JSON)?
- 如何避免過多錯誤碼造成維護負擔?
Practice Exercise
- 基礎:ExitCode 與 stderr(30 分)
- 進階:以檔案鎖模擬 IO 錯誤(2 小時)
- 專案:建立重試與告警機制(8 小時)
Assessment Criteria
- 功能完整性:錯誤可回傳
- 程式碼品質:健壯
- 效能優化:無多餘等待
- 創新性:可配置錯誤策略 ```
Case #9: 參數與路徑引號處理,避免命令列解析陷阱
Problem Statement(問題陳述)
業務場景:Orchestrator 以命令列傳遞 input/output,需正確處理含空白或特殊字元的路徑,避免轉檔失敗。
技術挑戰:Windows 命令列引號與跳脫規則容易踩雷。
影響範圍:批次穩定性與失敗率。
複雜度評級:低
Root Cause Analysis
直接原因:
- 檔名/路徑包含空白、括號等導致解析錯誤。
- 缺乏統一的引號包裝與還原策略。
- 某些 API 會自動移除引號,增加複雜度。
深層原因:
- 架構層面:通訊契約需明確規範資料格式。
- 技術層面:命令列解析細節不足。
- 流程層面:缺少針對特殊字元的測試。
Solution Design
解決策略:所有參數以雙引號包裹;Worker 使用 args 原樣取得;嚴禁在參數中自行拆解未引號的字串。建立對包含空白與特殊字元的測試用例。
實施步驟:
- Orchestrator 引號策略
- 實作細節:ProcessStartInfo.Arguments 以 $”"{path}"” 構造。
- 所需資源:.NET。
- 預估時間:0.25 天。
- 測試特殊字元
- 實作細節:生成含空白、括號、# 的路徑測試。
- 所需資源:測試檔案。
- 預估時間:0.5 天。
關鍵程式碼/設定:
var argsStr = $"\"{inputPath}\" \"{outputPath}\"";
var psi = new ProcessStartInfo("Converter.exe", argsStr) { UseShellExecute = false };
實際案例:文章提到「參數的傳遞是麻煩事,…arguments… parsing 的問題」,最終採行簡單可控的 arguments 方案並成功達成 2x 吞吐。
實作環境:同 Case #1
實測數據:
- 改善前:存在參數解析風險
- 改善後:引號策略後未影響 2x 吞吐
- 改善幅度:失敗風險降低(支撐穩定 2x)
Learning Points
- 命令列引號最佳實踐
- 製作包含特殊字元的測試集
- 契約與實作者雙方的對齊
技能要求:
- 必備:ProcessStartInfo 實務
- 進階:CLI 解析邊界測試
延伸思考:
- 是否改用臨時參數檔傳遞?
- 大量參數時的選項設計?
Practice Exercise
- 基礎:引號處理(30 分)
- 進階:自動化特殊字元測試(2 小時)
- 專案:引入參數檔/環境變數對照測試(8 小時)
Assessment Criteria
- 功能完整性:多種路徑均可運行
- 程式碼品質:清晰穩健
- 效能優化:無多餘開銷
- 創新性:通用參數組裝工具 ```
Case #10: 檢討跨 Process Lock 的必要性與範圍
Problem Statement(問題陳述)
業務場景:擔心跨 process 的資源衝突是否需要全域鎖。文章觀點:通常只需 process 內的 lock,除非共享跨 process 資源。
技術挑戰:判斷是否需要引入跨 process 同步機制(如 Mutex)。
影響範圍:複雜度、穩定性與併發效益。
複雜度評級:低
Root Cause Analysis
直接原因:
- 誤判需要全域鎖會抑制並行。
- Canon Codec 的限制在 process 內,跨 process 未必需要鎖。
- 沒有共享狀態時,全域鎖屬過度工程。
深層原因:
- 架構層面:資源共享界定不清。
- 技術層面:同步原語濫用。
- 流程層面:風險評估偏保守導致性能犧牲。
Solution Design
解決策略:明確界定共享資源範圍。轉檔輸入/輸出檔案彼此獨立時,不引入跨 process 鎖。保留同一 input 的重複處理檢查與輸出檔名唯一策略。
實施步驟:
- 資源梳理
- 實作細節:列出可能共享項:同檔案、同輸出名。
- 所需資源:無。
- 預估時間:0.25 天。
- 設計避免衝突
- 實作細節:唯一輸出名,避免兩 worker 處理同一 input。
- 所需資源:.NET。
- 預估時間:0.5 天。
關鍵程式碼/設定:
// Orchestrator 確保唯一輸出、避免重複分派
var assigned = ConcurrentDictionary<string, bool>();
foreach (var f in files)
{
if (assigned.TryAdd(f, true)) queue.Add((f, GetOutput(f)));
}
實際案例:作者說明通常只做 process 內 lock 即可,無需全域 lock;實作雙 process 並行最終成功。
實作環境:同 Case #1
實測數據:
- 改善前:過度鎖定可能阻礙併發
- 改善後:不加全域鎖而穩定達 2x 吞吐
- 改善幅度:並行效率提升(支撐 2x)
Learning Points
- 鎖的作用域選擇
- 避免過度工程導致性能倒退
- 用資料分派替代鎖
技能要求:
- 必備:併發資料結構
- 進階:競爭條件分析
延伸思考:
- 真需要跨 process 鎖時如何設計?
- 如何用檔案系統原子操作替代?
Practice Exercise
- 基礎:避免重複分派(30 分)
- 進階:模擬競爭條件測試(2 小時)
- 專案:加入檔案級唯一性保證(8 小時)
Assessment Criteria
- 功能完整性:無重複處理
- 程式碼品質:清楚鎖定策略
- 效能優化:無不必要鎖
- 創新性:資料驅動的去鎖化 ```
Case #11: 以暫存檔與原子改名避免輸出競爭
Problem Statement(問題陳述)
業務場景:兩個 process 同時輸出到相同資料夾,需避免檔名衝突或半寫入的檔案被使用。
技術挑戰:提供簡單的檔案層級併發安全。
影響範圍:資料正確性與後續流程可靠性。
複雜度評級:低
Root Cause Analysis
直接原因:
- 併發寫入可能覆蓋或產生壞檔。
- 半寫入檔案被其他流程提早讀取。
- 無原子性保證的寫入流程。
深層原因:
- 架構層面:缺少輸出檔生命周期管理。
- 技術層面:未使用原子改名技巧。
- 流程層面:缺少完成訊號的約定。
Solution Design
解決策略:Worker 寫入 output.tmp,完成後原子改名為 output.jpg。Orchestrator 僅消費 .jpg,忽略 .tmp。避免跨 process 鎖。
實施步驟:
- Worker 修改輸出策略
- 實作細節:先寫 .tmp,最後 File.Move(.tmp, .jpg)。
- 所需資源:.NET。
- 預估時間:0.5 天。
- Orchestrator/後續流程忽略 .tmp
- 實作細節:只掃描與使用 .jpg。
- 所需資源:無。
- 預估時間:0.25 天。
關鍵程式碼/設定:
var tmp = output + ".tmp";
CanonCodec.Convert(input, tmp);
File.Move(tmp, output, overwrite: true); // 視 .NET 版本採用替代 API
實際案例:雖文章未詳述此步驟,但雙 process 成功並行且穩定,通常即依賴此類檔案層級原子策略維持 2x 吞吐。
實作環境:同 Case #1
實測數據:
- 改善前:潛在競爭風險
- 改善後:穩定輸出,維持 2x 吞吐
- 改善幅度:可靠性顯著提升(支撐 2x)
Learning Points
- 用檔案系統原子操作實現並發安全
- 狀態檔與完成訊號
- 減少鎖的需求
技能要求:
- 必備:檔案 IO API
- 進階:原子操作與恢復策略
延伸思考:
- 加入雜湊驗證輸出完整性?
- 異常中斷的清理策略?
Practice Exercise
- 基礎:.tmp -> .jpg 原子改名(30 分)
- 進階:中斷恢復機制(2 小時)
- 專案:加入輸出校驗與清理(8 小時)
Assessment Criteria
- 功能完整性:無壞檔案
- 程式碼品質:錯誤處理完整
- 效能優化:無多餘等待
- 創新性:校驗與回滾 ```
Case #12: 量化 CPU 利用率與吞吐,建立性能驗收門檻
Problem Statement(問題陳述)
業務場景:需以數據證明優化有效:CPU 從低於 80% 提升至約 80%,吞吐從 1 張/70 秒提升至 2 張/70 秒。
技術挑戰:建立可重現的量測方法與報表。
影響範圍:性能驗收、迭代優化依據。
複雜度評級:低
Root Cause Analysis
直接原因:
- 無數據就難以評估優化價值。
- 缺失統一的量測口徑。
- 未做基線,難比較改動前後。
深層原因:
- 架構層面:性能治理缺失。
- 技術層面:工具與指標未標準化。
- 流程層面:未把量測納入日常流程。
Solution Design
解決策略:使用 Stopwatch 記錄整批與單張時間、記錄完成數量、用系統監視器觀察 CPU,形成前後對比報表。以「CPU ~80%、2 張/70 秒」做為驗收門檻。
實施步驟:
- 建基線
- 實作細節:單 process 基線測試。
- 所需資源:Stopwatch。
- 預估時間:0.5 天。
- 建自動化量測
- 實作細節:Orchestrator 記錄每張開始/結束時間。
- 所需資源:日誌/CSV。
- 預估時間:0.5 天。
關鍵程式碼/設定:
var start = DateTime.UtcNow;
int completed = 0;
// 每完成一張
Interlocked.Increment(ref completed);
// 結束後輸出報告
Console.WriteLine($"Completed:{completed}, Duration:{DateTime.UtcNow-start}");
實際案例:文章提供關鍵指標:CPU 約 80%,吞吐翻倍。
實作環境:同 Case #1
實測數據:
- 改善前:1 張/70 秒
- 改善後:2 張/70 秒;CPU ~80%
- 改善幅度:吞吐 2 倍
Learning Points
- 基線/對照/報表的基本功
- 避免以直覺做性能決策
- 指標即契約(SLO)
技能要求:
- 必備:Stopwatch、日誌
- 進階:簡易報表/圖表
延伸思考:
- 是否監控磁碟與記憶體指標?
- 加入長期趨勢分析?
Practice Exercise
- 基礎:記錄每張耗時(30 分)
- 進階:產出前後對照 CSV(2 小時)
- 專案:自動產出 HTML 報表(8 小時)
Assessment Criteria
- 功能完整性:數據完整
- 程式碼品質:清楚易用
- 效能優化:指標支持結論
- 創新性:可重複的量測框架 ```
Case #13: 將轉檔抽離降低耦合與部署風險
Problem Statement(問題陳述)
業務場景:原本轉檔內嵌在主程式,耦合高,部署升級風險大。抽離為 exe 後,更新與回滾更容易。
技術挑戰:模組邊界與契約設計、版本相容。
影響範圍:維護性、風險控制、交付效率。
複雜度評級:中
Root Cause Analysis
直接原因:
- 內嵌造成每次變更都需重發主程式。
- 無法針對轉檔模組獨立迭代。
- 無法以最小影響做性能試驗。
深層原因:
- 架構層面:模組邊界未清。
- 技術層面:無明確契約。
- 流程層面:變更管理成本高。
Solution Design
解決策略:Converter.exe 與 Orchestrator 以命令列契約解耦;部署上可獨立更新 converter,出現問題可快速回退。便於針對轉檔模組單獨壓測與替換。
實施步驟:
- 制定版本與契約
- 實作細節:版本資訊輸出、參數/ExitCode 穩定。
- 所需資源:版本管理。
- 預估時間:0.5 天。
- 部署管道調整
- 實作細節:分開發佈 converter 與主程式。
- 所需資源:打包腳本。
- 預估時間:0.5 天。
關鍵程式碼/設定:
// Worker 額外支援 --version
if (args.Length == 1 && args[0] == "--version") { Console.WriteLine("Converter 1.0.0"); return 0; }
實際案例:文章將轉檔抽離為 .exe 並由主程式啟動,最終穩定達成 2x 吞吐,且降低複雜 IPC 的需求。
實作環境:同 Case #1
實測數據:
- 改善前:單體耦合,變更風險大
- 改善後:模組化解耦,性能與維護性提升
- 改善幅度:在保持 2x 吞吐的同時降低交付風險
Learning Points
- 模組化與契約優先
- 用可替換部件降低風險
- 便於 A/B 版本實驗
技能要求:
- 必備:版本與契約管理
- 進階:部署自動化腳本
延伸思考:
- 是否能以插件機制替換 converter?
- 契約演進與相容策略?
Practice Exercise
- 基礎:–version 支援(30 分)
- 進階:獨立發佈 converter(2 小時)
- 專案:灰度釋出新 converter(8 小時)
Assessment Criteria
- 功能完整性:解耦與版本化
- 程式碼品質:契約清楚
- 效能優化:維持吞吐
- 創新性:灰度/回退設計 ```
Case #14: 接受 IPC 複雜度以換取巨量性能回報的性價比評估
Problem Statement(問題陳述)
業務場景:作者原先不願碰 IPC,但評估後發現 IPC 的麻煩不消耗大量運算,與單張 70 秒、上百張的轉檔工作相比,導入 IPC 顯著划算。
技術挑戰:將工程複雜度與性能收益量化,促成合理決策。
影響範圍:研發效率、交付時間、性能體驗。
複雜度評級:低
Root Cause Analysis
直接原因:
- IPC 使開發與維護變麻煩。
- 參數/回傳需處理 parsing 與協定。
- 害怕「工程負擔大於收益」。
深層原因:
- 架構層面:未建立性能 vs 成本評估模型。
- 技術層面:低估簡單 IPC 的性價比。
- 流程層面:缺少數據驅動的投資決策。
Solution Design
解決策略:以「70 秒/張、數百張」為規模,估算 IPC 帶來的額外開發時間與每張微不足道的執行開銷,對比預期 2x 吞吐的收益,形成明確的 ROI 報告,指導決策。
實施步驟:
- 建立 ROI 模型
- 實作細節:估計開發工時 vs 時間節省。
- 所需資源:歷史任務量統計。
- 預估時間:0.5 天。
- 以 PoC 驗證收益
- 實作細節:雙 process 實測 2x 吞吐。
- 所需資源:同 Case #3。
- 預估時間:0.5 天。
關鍵程式碼/設定:
無需代碼:此案例重在決策方法。核心依據:單張70秒、雙進程70秒出兩張,CPU~80%。
實際案例:作者評估後接受 IPC 的麻煩,最終把轉檔抽為 exe 並以兩 process 運行,吞吐翻倍。
實作環境:同 Case #1
實測數據:
- 改善前:1 張/70 秒
- 改善後:2 張/70 秒;IPC 開銷可忽略
- 改善幅度:吞吐 2 倍,開發成本可接受
Learning Points
- 將工程成本與性能收益量化
- 以數據說服決策
- 持有「簡單可行」偏好
技能要求:
- 必備:粗略估算與比較
- 進階:以真實數據校準模型
延伸思考:
- 長期運維成本如何評估?
- 未來擴展(>2 process)是否改變 ROI?
Practice Exercise
- 基礎:撰寫簡單 ROI 計算(30 分)
- 進階:加入多批次場景估算(2 小時)
- 專案:建立決策模板(8 小時)
Assessment Criteria
- 功能完整性:包含主要因素
- 程式碼品質:若有工具,簡單可靠
- 效能優化:能支持決策
- 創新性:可視化報告 ```
Case #15: 以雙 process 運行下的 CPU 使用率分析(80% 而非 100%)
Problem Statement(問題陳述)
業務場景:雙 process 後 CPU 約 80%,未達 100%。需理解差距原因,避免不切實際追求滿載造成副作用。
技術挑戰:分析瓶頸可能轉移到 I/O、編碼內部等待或 OS 排程。
影響範圍:優化方向與預期管理。
複雜度評級:中
Root Cause Analysis
直接原因:
- Canon Codec 內部可能含有等待/同步導致非純 CPU 受限。
- 磁碟 I/O 或記憶體頻寬造成階段性阻塞。
- OS 排程與其他背景工作占用。
深層原因:
- 架構層面:單純堆疊 worker 未必達上限。
- 技術層面:需要辨識多資源瓶頸。
- 流程層面:設定現實的性能目標。
Solution Design
解決策略:接受 80% 為當前最佳,在不導致副作用的前提下觀察是否有調整空間(如檔案分散在不同磁碟)。將「70 秒兩張」作為穩定指標,而非盲目追求 100% CPU。
實施步驟:
- 簡單資源觀測
- 實作細節:觀察磁碟佔用、I/O 佇列。
- 所需資源:PerfMon。
- 預估時間:0.5 天。
- 預期管理
- 實作細節:將 2x 吞吐列為達標,記錄環境限制。
- 所需資源:文件化。
- 預估時間:0.25 天。
關鍵程式碼/設定:
無需代碼:重在指標與預期管理。CPU~80% + 吞吐2x 即達成文章驗證結果。
實際案例:文章指出 CPU 利用率飆到 80%,離 100% 有距離但吞吐已翻倍。
實作環境:同 Case #1
實測數據:
- 現況:CPU ~80%,兩張/70 秒
- 目標:維持穩定 2x 吞吐
- 評估:無需過度追求 100% CPU
Learning Points
- 指標之間的平衡(CPU vs 吞吐)
- 不同資源的瓶頸辨識
- 避免過度優化
技能要求:
- 必備:系統資源觀測
- 進階:瓶頸定位方法
延伸思考:
- 若升級硬體是否能更靠近 100%?
- 增加到 3+ process 會不會反而退步?
Practice Exercise
- 基礎:記錄 CPU 與 I/O 指標(30 分)
- 進階:不同磁碟位置測試(2 小時)
- 專案:撰寫小報告:為何 80% 也足夠(8 小時)
Assessment Criteria
- 功能完整性:指標齊全
- 程式碼品質:若有腳本,清晰
- 效能優化:合理結論
- 創新性:提出具體優化建議 ```
Case #16: 以最小開銷維持長時任務的 Process Pool
Problem Statement(問題陳述)
業務場景:轉檔每張 70 秒,頻繁啟停 process 的相對成本雖小,但仍可用「長駐 process 連續吃任務」進一步降低風險與雜訊。
技術挑戰:用簡單方式讓同一 worker 連續處理多張,避免啟動風險。
影響範圍:穩定性、啟動失敗率、日誌管理。
複雜度評級:中
Root Cause Analysis
直接原因:
- 進程啟動有微小但非零成本與失敗機率。
- 長任務中累積的小風險會顯著化。
- 現有 IPC 簡單,仍可在不複雜化的情況下小幅優化。
深層原因:
- 架構層面:任務拉式 vs 啟動即做完式的取捨。
- 技術層面:保持簡潔的同時提升穩定。
- 流程層面:對失敗重試與資源釋放的管理。
Solution Design
解決策略:為每個 worker 配給一份「清單檔」或「目錄清單」,一次啟動處理多張,處理完畢退出;Orchestrator 分配工作區避免重疊。仍使用 arguments + exit code,維持簡單。
實施步驟:
- 清單檔格式定義
-
實作細節:每行 input output。 - 所需資源:.NET。
- 預估時間:0.5 天。
-
- Orchestrator 分塊分派
- 實作細節:每個 worker 一塊,處完再派下一塊。
- 所需資源:.NET。
- 預估時間:0.5 天。
關鍵程式碼/設定:
// Orchestrator 建立批次清單檔後啟動
File.WriteAllLines(listPath, jobs.Select(j => $"{j.input}|{j.output}"));
var psi = new ProcessStartInfo("Converter.exe", $"--list \"{listPath}\"");
實際案例:文章雖指出啟動 process 的成本可接受,但為維持穩定與簡潔,可進一步批次化處理而不影響 2x 吞吐。
實作環境:同 Case #1
實測數據:
- 改善前:逐張啟動仍能達 2x
- 改善後:批次啟動維持 2x,穩定性提升
- 改善幅度:穩定性提升(吞吐不變)
Learning Points
- Process pool 與批次任務
- 在不增加複雜度下的工程優化
- 失敗隔離與重試策略
技能要求:
- 必備:檔案 IO、流程控制
- 進階:批次分配策略
延伸思考:
- 是否可用命名管道進一步長駐?
- 如何平衡簡潔與靈活?
Practice Exercise
- 基礎:實作 –list 模式(30 分)
- 進階:分塊分派與回報(2 小時)
- 專案:落地到現有 Orchestrator(8 小時)
Assessment Criteria
- 功能完整性:批次處理可行
- 程式碼品質:清晰,易維護
- 效能優化:穩定性提升
- 創新性:分塊與回報機制 ```
Case #17: 硬體升級(Core2 Quad Q9450)與軟體優化的取捨
Problem Statement(問題陳述)
業務場景:文章最後拋出是否該換 Q9450 的念頭。需對「多 process 可橫向擴展至更多核心」與「硬體成本」做初步評估。
技術挑戰:在現有效能(2x 吞吐)下,評估升級能否帶來線性或次線性收益。
影響範圍:成本、交期、可擴展性。
複雜度評級:中
Root Cause Analysis
直接原因:
- 現在雙核心已用兩個 process 吃滿至 ~80%。
- 升級到四核心可望增加並行數至 3-4。
- 但 I/O 與 Codec 內部行為可能限制線性成長。
深層原因:
- 架構層面:軟體能否隨核心數擴展。
- 技術層面:新瓶頸出現的可能(I/O)。
- 流程層面:成本/效益/風險衡量。
Solution Design
解決策略:基於現有 Orchestrator-Worker 架構,先以現硬體模擬多 worker 的行為(降低單 worker 優先權、引入空閒等待)評估 I/O 壓力;若趨勢良好,再進行硬體升級試點。
實施步驟:
- 軟模擬擴展測試
- 實作細節:嘗試 3-4 worker 在雙核上觀察 I/O 與抖動。
- 所需資源:PerfMon。
- 預估時間:1 天。
- 小規模硬體試點
- 實作細節:於單台升級機測試吞吐。
- 所需資源:Q9450 測試機。
- 預估時間:1-2 天。
關鍵程式碼/設定:
int targetWorkers = Environment.ProcessorCount; // 升級後可調
// 仍用 arguments + exit code,觀測吞吐與資源佔用
實際案例:文章未進一步測,但提出升級念頭。此評估框架可銜接現有 2x 吞吐成果進一步擴展。
實作環境:同 Case #1
實測數據:
- 基線:2x 吞吐,CPU ~80%
- 目標:評估多核心下的可擴展性
- 結論:先以軟模擬與試點降低風險
Learning Points
- 軟硬體共演化的策略
- 以試點驗證投資回報
- 預估非線性效益
技能要求:
- 必備:性能測試、指標收集
- 進階:容量規劃
延伸思考:
- I/O 子系統是否需同步升級?
- 工作集與記憶體頻寬影響?
Practice Exercise
- 基礎:讀取 ProcessorCount 自動設定 worker(30 分)
- 進階:模擬 3-4 worker 的 I/O 壓力(2 小時)
- 專案:撰寫升級評估報告模板(8 小時)
Assessment Criteria
- 功能完整性:可自動調整 worker
- 程式碼品質:可讀與可配
- 效能優化:有數據支持建議
- 創新性:分層升級與試點方案 ```
Case #18: 多任務流水線拆分與「主瓶頸優先」的流程重整
Problem Statement(問題陳述)
業務場景:最初以 ThreadPool 將多任務堆疊並行,但非轉檔任務很快完成,無法改善總時間。需要流程重整:以轉檔為主軸設計整體管線。
技術挑戰:重新排列任務順序、資源分配,讓轉檔長任務成為最優先調度對象。
影響範圍:整體完成時間、資源利用率。
複雜度評級:中
Root Cause Analysis
直接原因:
- 將各任務一視同仁地並行,無法針對主瓶頸優化。
- 短任務先完成,沒有幫助縮短總時間。
- 缺少「瓶頸優先」的管線設計。
深層原因:
- 架構層面:未採用流水線/批處理視角。
- 技術層面:未把轉檔抽為獨立專屬階段。
- 流程層面:優先級策略不明確。
Solution Design
解決策略:重整為「主瓶頸優先」管線:核心資源集中服務轉檔(雙 process),其他快速任務放在前後或間隙執行,不與轉檔爭用核心資源,讓轉檔持續滿載運轉。
實施步驟:
- 任務分類與排序
- 實作細節:分短任務/長任務,轉檔專屬階段。
- 所需資源:無。
- 預估時間:0.5 天。
- 調度策略落地
- 實作細節:轉檔持續兩 worker;其他任務在邊緣執行。
- 所需資源:.NET。
- 預估時間:0.5 天。
關鍵程式碼/設定:
// 粗略示意:先掃描與準備,後轉檔雙工,最後收尾與索引
RunPreparationJobs();
RunConversionWithTwoWorkers();
RunPostJobs();
實際案例:文章描述非轉檔任務很快做完,真正卡在轉檔。透過流程重整與雙 process,達到 2x 吞吐。
實作環境:同 Case #1
實測數據:
- 改善前:短任務先完,總時間不降
- 改善後:轉檔飽和運行,2x 吞吐
- 改善幅度:總時間顯著縮短
Learning Points
- 瓶頸優先原則(Theory of Constraints)
- 流水線化思維
- 調度與資源隔離
技能要求:
- 必備:任務拆分與優先級
- 進階:管線設計與監控
延伸思考:
- 是否可進一步重疊前後處理與轉檔?
- 如何以事件驅動進一步優化?
Practice Exercise
- 基礎:列出任務並分類(30 分)
- 進階:實作簡單三階段管線(2 小時)
- 專案:管線監控面板(8 小時)
Assessment Criteria
- 功能完整性:管線正確運行
- 程式碼品質:結構清楚
- 效能優化:縮短總時間
- 創新性:動態優先級 ```
Case #19: 可靠的 Process 啟動、監控與回收
Problem Statement(問題陳述)
業務場景:Orchestrator 須穩定啟動 Converter.exe,監聽其 stdout/stderr,並在異常時回收與重試,避免殭屍進程與資源洩漏。
技術挑戰:Process API 正確用法、逾時與回收。
影響範圍:穩定性與可觀測性。
複雜度評級:中
Root Cause Analysis
直接原因:
- Process 啟動參數/環境錯誤導致失敗。
- 未正確處理 WaitForExit/Timeout 造成資源懸掛。
- 未收集日誌導致難以診斷。
深層原因:
- 架構層面:缺少監控與回收策略。
- 技術層面:忽略邊界條件。
- 流程層面:缺失故障演練。
Solution Design
解決策略:標準化 Process 啟動:UseShellExecute=false、重定向輸出;設置逾時與取消;退出碼分析與重試;確保 Dispose。記錄 stdout/stderr 供診斷。
實施步驟:
- 啟動與監控封裝
- 實作細節:建立 RunConverterAsync 包裝。
- 所需資源:.NET。
- 預估時間:0.5 天。
- 逾時與回收
- 實作細節:取消 token、Kill 後再收集輸出。
- 所需資源:.NET。
- 預估時間:0.5 天。
關鍵程式碼/設定:
async Task<int> RunConverterAsync(string inF, string outF, TimeSpan timeout, CancellationToken ct)
{
var psi = new ProcessStartInfo("Converter.exe", $"\"{inF}\" \"{outF}\"")
{ UseShellExecute=false, RedirectStandardOutput=true, RedirectStandardError=true };
using var p = new Process { StartInfo = psi, EnableRaisingEvents = true };
p.Start();
var exited = await Task.WhenAny(p.WaitForExitAsync(ct), Task.Delay(timeout, ct)) == p.WaitForExitAsync(ct);
if (!exited) { try { p.Kill(entireProcessTree:true); } catch {} await p.WaitForExitAsync(); return -1; }
var stdout = await p.StandardOutput.ReadToEndAsync();
var stderr = await p.StandardError.ReadToEndAsync();
// 記錄 stdout/stderr
return p.ExitCode;
}
實際案例:文章雖未細述,但成功的雙 process 長時間執行,暗示正確處理了啟動/退出流程。
實作環境:同 Case #1
實測數據:
- 改善前:潛在殭屍/診斷困難
- 改善後:穩定運行支撐 2x 吞吐
- 改善幅度:穩定性與可診斷性提升
Learning Points
- Process API 實務
- 超時與回收
- 日誌與可觀測性
技能要求:
- 必備:Task/async、Process
- 進階:取消與資源管理
延伸思考:
- 是否需要指標上報(Prometheus)?
- 集中式日誌收集?
Practice Exercise
- 基礎:封裝 RunConverterAsync(30 分)
- 進階:加入逾時/重試(2 小時)
- 專案:集中記錄 stdout/stderr(8 小時)
Assessment Criteria
- 功能完整性:穩定啟停
- 程式碼品質:封裝與錯誤處理
- 效能優化:無阻塞
- 創新性:指標上報 ```
Case #20: 以資料驅動的重試與故障隔離策略
Problem Statement(問題陳述)
業務場景:批次量大時,少數檔案失敗不應拖累整批。需要以 ExitCode 與錯誤訊息驅動重試與隔離清單。
技術挑戰:避免無限重試、區分可重試與不可重試、保證吞吐。
影響範圍:完成率、用戶體驗、作業效率。
複雜度評級:中
Root Cause Analysis
直接原因:
- 部分檔案可能因 IO/權限/損壞失敗。
- 無策略會造成卡批或無限重試。
- 重試會影響整體吞吐與資源配置。
深層原因:
- 架構層面:缺少故障隔離機制。
- 技術層面:缺少錯誤分類(可重試/不可重試)。
- 流程層面:缺乏事後處理清單。
Solution Design
解決策略:根據 ExitCode 決定重試次數(如 IO 類重試 2 次、一般錯誤不重試);失敗寫入隔離清單,批次結束後另行處理。保持兩 worker 飽和,保障吞吐。
實施步驟:
- 定義重試規則
- 實作細節:ExitCode->策略表。
- 所需資源:規則檔。
- 預估時間:0.5 天。
- Orchestrator 實作
- 實作細節:重試計數、隔離清單輸出。
- 所需資源:.NET。
- 預估時間:1 天。
關鍵程式碼/設定:
bool ShouldRetry(int exitCode) => exitCode == 3 /* IO_ERROR */;
async Task ProcessJob(Job job)
{
int attempts = 0;
while (attempts++ < 3)
{
var code = await RunConverterAsync(job.In, job.Out, TimeSpan.FromMinutes(5), CancellationToken.None);
if (code == 0) return;
if (!ShouldRetry(code)) break;
await Task.Delay(TimeSpan.FromSeconds(2));
}
File.AppendAllText("failed.csv", $"{job.In},{job.Out}\n");
}
實際案例:文章以雙 process 運行穩定完成大量轉檔,重試與隔離策略有助維持 2x 吞吐與整體完成率。
實作環境:同 Case #1
實測數據:
- 改善前:單錯誤可能卡住流程
- 改善後:失敗隔離,吞吐 2x 依舊
- 改善幅度:完成率與穩定性提升
Learning Points
- 錯誤分類與策略
- 故障不擴散原則
- 吞吐與可靠性的平衡
技能要求:
- 必備:流程控制與檔案 IO
- 進階:策略表設計與外部化
延伸思考:
- 是否需要告警/通知?
- 是否提供自動重跑介面?
Practice Exercise
- 基礎:failed.csv 輸出(30 分)
- 進階:策略表外部化(2 小時)
- 專案:重跑工具(8 小時)
Assessment Criteria
- 功能完整性:重試與隔離正常
- 程式碼品質:清晰、可維護
- 效能優化:吞吐不受大幅影響
- 創新性:策略可配置 ```
案例分類
- 按難度分類
- 入門級(適合初學者)
- Case 3, 5, 7, 8, 9, 12
- 中級(需要一定基礎)
- Case 1, 2, 4, 6, 10, 11, 15, 16, 18, 19, 20
- 高級(需要深厚經驗)
- Case 17
- 入門級(適合初學者)
- 按技術領域分類
- 架構設計類
- Case 2, 4, 10, 13, 16, 17, 18
- 效能優化類
- Case 1, 3, 6, 7, 12, 15
- 整合開發類
- Case 5, 8, 9, 11, 19, 20
- 除錯診斷類
- Case 7, 12, 19, 20
- 安全防護類
- Case 11(檔案一致性/原子性屬資料安全範疇的基本面)
- 架構設計類
- 按學習目標分類
- 概念理解型
- Case 1, 2, 10, 12, 15
- 技能練習型
- Case 5, 8, 9, 11, 19
- 問題解決型
- Case 3, 4, 6, 7, 18, 20
- 創新應用型
- Case 13, 16, 17
- 概念理解型
案例關聯圖(學習路徑建議)
- 建議先學:
- Case 1(辨識瓶頸與多執行緒無效的原因)
- Case 2(黑箱庫不可重入的驗證方法)
- Case 3(雙 process PoC 快速實證)
- Case 12(指標量測與驗收門檻)
- 依賴關係:
- Case 4(Orchestrator-Worker)依賴 Case 1-3 的結論
- Case 5、8、9(IPC 基礎、Worker 最小面積、引號處理)依賴 Case 4 的架構
- Case 6(調度與併發維持)依賴 Case 4-5
- Case 10-11(鎖與輸出原子性)依賴 Case 4-6
- Case 7(不變慢的驗證)與 Case 12 並行,持續驗證 Case 4-6 的成效
- Case 19(啟動監控回收)與 Case 20(重試隔離)強化 Case 4-6 的穩定性
- Case 13(模組化維護)在 Case 4 落地後進一步完善
- Case 15(CPU 80% 分析)與 Case 12 結合,指導優化方向
- Case 16(Process Pool)在 Case 5、8 穩定後優化
- Case 17(硬體升級)在整體軟體穩定後評估
- Case 18(流程重整)貫穿 Case 4-7 的管線化思維
- 完整學習路徑建議: 1) 基礎認知與驗證:Case 1 → Case 2 → Case 3 → Case 12 → Case 7 2) 架構落地:Case 4 → Case 5 → Case 8 → Case 9 3) 併發與穩定:Case 6 → Case 10 → Case 11 → Case 19 → Case 20 4) 流程與維護:Case 18 → Case 13 → Case 16 → Case 15 5) 擴展與投資:Case 17(硬體升級評估)
此路徑從問題診斷到 PoC 證實,再到架構落地、穩定性工程、流程重整,最後進入容量規劃與硬體評估,完整覆蓋文章情境並可直接用於實戰教學與專案演練。