GST.Rpa.Core — 狀態機引擎
| 項目 | 內容 |
|---|---|
| 適用對象 | GST 內部開發人員 |
| 前置知識 | C# / .NET 8 / DI / State Machine 概念 |
| 閱讀時間 | 20 分鐘 |
| 最後更新 | 2026-04-04 |
| 版本 | 1.0.0 |
模組職責
GST.Rpa.Core 是 GST.RPA 系統的基礎層,負責定義與執行 RPA 工作流程。核心採用 Stateless 狀態機函式庫,提供:
- 工作流程建模 — 以 Fluent Builder 定義 State / Transition / Guard
- 狀態機驅動執行 —
RpaEngine依序執行狀態 Handler,透過 Trigger 驅動狀態轉移 - Pause / Resume — Thread-safe 的暫停與恢復機制
- 錯誤復原策略 — 每個 State 可獨立設定 Retry / Skip / Abort
- DOT Graph 匯出 — 將工作流程輸出為 Graphviz DOT 格式,便於視覺化
此模組不依賴任何 UI 框架,可獨立於 WPF / Blazor 使用。
NuGet 引用與相依
專案使用 Central Package Management,版本定義於 Directory.Packages.props:
| 套件 | 版本 | 用途 |
|---|---|---|
Stateless | 5.20.1 | 有限狀態機核心引擎 |
System.Text.Json | 8.0.5 | JSON 序列化 |
Microsoft.Extensions.DependencyInjection | 8.0.1 | DI 容器抽象 |
GST.Rpa.Core.csproj 的 PackageReference(版本由 Central Package Management 統一管理):
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Stateless" />
<PackageReference Include="System.Text.Json" />
</ItemGroup>
</Project>
核心 API
IRpaEngine — 引擎介面
IRpaEngine 定義工作流程引擎的完整操作契約,位於 GST.Rpa.Core.Abstractions namespace。
namespace GST.Rpa.Core.Abstractions;
public interface IRpaEngine
{
/// <summary>狀態轉移時觸發</summary>
event EventHandler<StateChangedEventArgs>? StateChanged;
/// <summary>引擎目前狀態 (Idle / Running / Paused / Error / Completed)</summary>
RpaEngineState CurrentEngineState { get; }
/// <summary>目前執行中的 workflow state 名稱,閒置時為 null</summary>
string? CurrentStateName { get; }
/// <summary>從 InitialState 執行至完成</summary>
Task ExecuteAsync(RpaWorkflow workflow, RpaContext context,
CancellationToken cancellationToken = default);
/// <summary>暫停執行(等待當前 state handler 完成後暫停)</summary>
Task PauseAsync();
/// <summary>恢復暫停的執行</summary>
Task ResumeAsync();
/// <summary>匯出 DOT Graph 字串,可供 Graphviz / D3-graphviz 使用</summary>
string ExportDotGraph(RpaWorkflow workflow);
}
RpaEngineState 列舉
public enum RpaEngineState
{
Idle, // 閒置,可接受新工作流程
Running, // 執行中
Paused, // 已暫停
Error, // 執行錯誤
Completed // 已完成
}
Engine Lifecycle 狀態機
RpaEngine 本身就是一個狀態機,5 個 RpaEngineState 之間的轉移由 ExecuteAsync / PauseAsync / ResumeAsync 觸發;終止時自動進入 Completed 或 Error:
各狀態的可觀察行為:
| 狀態 | CurrentStateName | 可呼叫的 API | 轉出條件 |
|---|---|---|---|
| Idle | null | ExecuteAsync | 呼叫 ExecuteAsync → Running |
| Running | workflow 內部 state 名稱 | PauseAsync、自然完成 | PauseAsync → Paused;正常完成 → Completed;例外 → Error |
| Paused | 暫停時的 state 名稱 | ResumeAsync | ResumeAsync → Running |
| Completed | 最後執行的 state 名稱 | ExecuteAsync(新 workflow) | 重新呼叫 ExecuteAsync → Running |
| Error | 失敗時的 state 名稱 | ExecuteAsync(新 workflow) | 重新呼叫 ExecuteAsync → Running |
兩個容易混淆的狀態:
RpaEngineState.Running期間,內部會在多個RpaState.Name之間切換——StateChanged事件追蹤的是 workflow 內部 state,引擎本身仍維持RunningError不是終止狀態——可以重新呼叫ExecuteAsync重新啟動引擎;只有物件被 GC 時才真正進入[*]
StateChangedEventArgs
public class StateChangedEventArgs : EventArgs
{
public string? PreviousStateName { get; } // 前一狀態(初始進入時為 null)
public string CurrentStateName { get; } // 當前狀態
public RpaEngineState EngineState { get; } // 引擎狀態
public DateTime Timestamp { get; } // 狀態變更時間 (UTC)
}
RpaWorkflow — Fluent Builder
RpaWorkflow 是不可變 (Immutable) 的工作流程定義,透過內建的 RpaWorkflowBuilder 以 Fluent API 建構。
public class RpaWorkflow
{
public string Name { get; }
public string? Description { get; init; }
public IReadOnlyList<RpaState> States { get; }
public IReadOnlyList<RpaTransition> Transitions { get; }
public string InitialStateName { get; }
public RecoveryStrategy DefaultErrorPolicy { get; init; } // 預設: Abort
/// <summary>建立 Builder</summary>
public static RpaWorkflowBuilder CreateBuilder(string name);
}
Builder API
public class RpaWorkflowBuilder
{
// 設定描述
RpaWorkflowBuilder WithDescription(string description);
// 新增狀態(RpaState 物件)
RpaWorkflowBuilder AddState(RpaState state);
// 新增狀態(名稱 + optional handler)
RpaWorkflowBuilder AddState(string name, Func<RpaContext, CancellationToken, Task>? handler = null);
// 新增轉移
RpaWorkflowBuilder AddTransition(string from, string to, string trigger,
Func<RpaContext, bool>? guard = null);
// 設定初始狀態(預設為第一個加入的 State)
RpaWorkflowBuilder SetInitialState(string stateName);
// 設定預設錯誤復原策略
RpaWorkflowBuilder WithDefaultErrorPolicy(RecoveryStrategy strategy);
// 建構不可變的 RpaWorkflow
RpaWorkflow Build();
}
自動轉移生成:若未定義任何 Transition,Builder 會自動依 State 加入順序建立 "Next" trigger 的線性轉移鏈。
RpaState — 狀態定義
public record RpaState(
string Name,
Func<RpaContext, CancellationToken, Task>? Handler = null)
{
/// <summary>進入狀態時執行</summary>
public Func<RpaContext, CancellationToken, Task>? EntryAction { get; init; }
/// <summary>離開狀態時執行</summary>
public Func<RpaContext, CancellationToken, Task>? ExitAction { get; init; }
/// <summary>錯誤復原策略(覆寫 workflow 預設值)</summary>
public RecoveryStrategy? ErrorRecovery { get; init; }
/// <summary>Retry 策略的最大重試次數(預設: 3)</summary>
public int MaxRetryCount { get; init; } = 3;
/// <summary>重試間隔(預設: 1 秒)</summary>
public TimeSpan RetryDelay { get; init; } = TimeSpan.FromSeconds(1);
}
每個 State 的執行順序為:EntryAction → Handler → ExitAction。若任一步驟拋出例外,依 ErrorRecovery 策略處理。
RpaTransition — 轉移定義
public record RpaTransition(
string From, // 來源狀態名稱
string To, // 目標狀態名稱
string Trigger) // 觸發事件名稱
{
/// <summary>Guard 條件:回傳 true 時才允許轉移</summary>
public Func<RpaContext, bool>? Guard { get; init; }
}
RpaContext — 執行上下文
RpaContext 在整個 workflow 執行期間共享,各 State Handler 透過它傳遞資料。
public class RpaContext
{
/// <summary>Key-value 變數儲存,跨 State 共享</summary>
public Dictionary<string, object?> Variables { get; }
/// <summary>目前執行中的 State 名稱</summary>
public string? CurrentStateName { get; internal set; }
/// <summary>最近一次截圖</summary>
public byte[]? Screenshot { get; set; }
/// <summary>目前操作的 UI 元素</summary>
public UIElementInfo? CurrentElement { get; set; }
/// <summary>取消 Token</summary>
public CancellationToken CancellationToken { get; init; }
/// <summary>工作流程開始時間 (UTC)</summary>
public DateTime StartTime { get; }
/// <summary>執行日誌</summary>
public List<string> ExecutionLog { get; }
/// <summary>索引子:快速存取 Variables</summary>
public object? this[string key] { get; set; }
}
RecoveryStrategy — 錯誤復原策略
public enum RecoveryStrategy
{
Retry, // 重試失敗的 State,最多 MaxRetryCount 次
Skip, // 跳過失敗的 State,繼續執行下一個
Abort // 中止整個 Workflow,拋出 WorkflowException
}
| 策略 | 行為 | 適用場景 |
|---|---|---|
Retry | 依 MaxRetryCount 與 RetryDelay 重試,超過次數拋出 WorkflowException | 網路不穩、UI 元素暫時不可用 |
Skip | 記錄錯誤至 ExecutionLog,跳過該 State 繼續 | 非關鍵步驟(如截圖、日誌上傳) |
Abort | 立即拋出 WorkflowException,引擎轉為 Error 狀態 | 關鍵步驟失敗(預設行為) |
例外階層
所有 GST.RPA 例外繼承自 GstRpaException,形成以下樹狀結構:
Exception
└── GstRpaException ← 基底例外
├── WorkflowException ← 工作流程執行錯誤(含 StateName)
├── AutomationException ← UI 自動化錯誤
│ ├── ElementNotFoundException ← 找不到 UI 元素(含 Identifier)
│ └── ElementNotInteractableException ← UI 元素無法互動(含 ElementName, Reason)
├── LicenseException ← 授權相關錯誤
│ ├── LicenseExpiredException ← 授權已過期(含 ExpirationDate)
│ └── FeatureNotLicensedException ← 功能未授權(含 LicenseFeature)
└── ScanException ← UI 元素掃描錯誤
各例外重點屬性
| 例外類別 | 額外屬性 | 說明 |
|---|---|---|
WorkflowException | string? StateName | 發生錯誤的 State 名稱 |
ElementNotFoundException | string Identifier | 搜尋用的元素識別字串 |
ElementNotInteractableException | string ElementName, string Reason | 無法互動的元素與原因 |
LicenseExpiredException | DateTime? ExpirationDate | 授權到期日 |
FeatureNotLicensedException | LicenseFeature Feature | 未授權的功能旗標 |
使用範例
以下示範一個 3 狀態工作流程:Init → Process → Complete,包含 Handler、錯誤復原與 DOT Graph 匯出。
using GST.Rpa.Core.Abstractions;
using GST.Rpa.Core.Enums;
using GST.Rpa.Core.Events;
using GST.Rpa.Core.Models;
using GST.Rpa.Core.Services;
// 1. 定義狀態
var initState = new RpaState("Init", async (ctx, ct) =>
{
ctx["StartedAt"] = DateTime.UtcNow;
ctx.ExecutionLog.Add("Workflow initialized.");
})
{
EntryAction = async (ctx, ct) =>
ctx.ExecutionLog.Add("Entering Init state...")
};
var processState = new RpaState("Process", async (ctx, ct) =>
{
// 模擬業務邏輯
await Task.Delay(500, ct);
ctx["ItemsProcessed"] = 42;
ctx.ExecutionLog.Add($"Processed {ctx["ItemsProcessed"]} items.");
})
{
ErrorRecovery = RecoveryStrategy.Retry,
MaxRetryCount = 3,
RetryDelay = TimeSpan.FromSeconds(2)
};
var completeState = new RpaState("Complete", async (ctx, ct) =>
{
var count = ctx["ItemsProcessed"];
ctx.ExecutionLog.Add($"Workflow completed. Total items: {count}");
});
// 2. 使用 Fluent Builder 建構 Workflow
var workflow = RpaWorkflow.CreateBuilder("Demo Workflow")
.WithDescription("三狀態示範工作流程")
.AddState(initState)
.AddState(processState)
.AddState(completeState)
.AddTransition("Init", "Process", "Next")
.AddTransition("Process", "Complete", "Next")
.WithDefaultErrorPolicy(RecoveryStrategy.Abort)
.Build();
// 3. 建立引擎並訂閱事件
IRpaEngine engine = new RpaEngine();
engine.StateChanged += (sender, e) =>
{
Console.WriteLine($"[{e.Timestamp:HH:mm:ss}] " +
$"{e.PreviousStateName ?? "(start)"} → {e.CurrentStateName} " +
$"(Engine: {e.EngineState})");
};
// 4. 執行工作流程
var context = new RpaContext();
using var cts = new CancellationTokenSource();
try
{
await engine.ExecuteAsync(workflow, context, cts.Token);
Console.WriteLine($"Engine State: {engine.CurrentEngineState}"); // Completed
}
catch (Exception ex)
{
Console.WriteLine($"Workflow failed: {ex.Message}");
}
// 5. 輸出執行日誌
foreach (var log in context.ExecutionLog)
{
Console.WriteLine($" LOG: {log}");
}
// 6. 匯出 DOT Graph
var dot = engine.ExportDotGraph(workflow);
Console.WriteLine(dot);
DOT Graph 匯出
ExportDotGraph 利用 Stateless 內建的 UmlDotGraph.Format() 產生 DOT 語法,可直接餵入 Graphviz 或嵌入 D3-graphviz 前端元件。
var dot = engine.ExportDotGraph(workflow);
File.WriteAllText("workflow.dot", dot);
// 使用 Graphviz CLI: dot -Tpng workflow.dot -o workflow.png
範例輸出(基於上述 3 狀態 workflow):
digraph {
compound=true;
node [shape=Mrecord];
rankdir="LR";
Init [label="Init"];
Process [label="Process"];
Complete [label="Complete"];
__Completed__ [label="__Completed__"];
Init -> Process [style="solid", label="Next"];
Process -> Complete [style="solid", label="Next"];
Complete -> __Completed__ [style="solid", label="Next"];
}
提示:
__Completed__是引擎內部的終結虛擬狀態。當最後一個 State 沒有明確的 outgoing transition 時,引擎會自動加入指向__Completed__的"Next"transition。
架構決策
為什麼選擇 Stateless 函式庫?
| 考量 | 說明 |
|---|---|
| Thread-safe | StateMachine<TState, TTrigger> 預設為 thread-safe,適合 Pause/Resume 的並行控制場景 |
| 輕量 | 零外部依賴,純 .NET Standard 2.0 相容,不引入額外 runtime 負擔 |
| DOT 匯出 | 內建 UmlDotGraph.Format() 可直接將狀態機定義匯出為 DOT Graph,無需手動建構 |
| Guard 支援 | 原生 PermitIf API 支援條件式轉移,與 RpaTransition.Guard 自然對應 |
| 成熟穩定 | NuGet 下載量超過千萬,社群活躍維護 |
Pause / Resume 設計
引擎使用 TaskCompletionSource 實現 Pause/Resume:
ExecuteAsync主迴圈在每次狀態執行前await _pauseSignal.TaskPauseAsync()建立新的未完成TaskCompletionSource,主迴圈自然阻塞ResumeAsync()呼叫TrySetResult()解除阻塞
此設計確保:
- 暫停發生在 State 之間,不會中斷正在執行的 Handler
- 使用
volatile標記_engineState,保證跨執行緒的可見性 - 搭配
CancellationToken支援取消,避免永久掛起
錯誤復原策略
每個 RpaState 可獨立覆寫 workflow 層級的 DefaultErrorPolicy:
- State 層級優先:
state.ErrorRecovery ?? workflow.DefaultErrorPolicy - Retry 含延遲:透過
MaxRetryCount與RetryDelay控制重試行為 - Skip 記錄日誌:跳過時自動寫入
ExecutionLog,不影響後續 State - Abort 拋出 WorkflowException:附帶
StateName以利偵錯
這讓開發者可以在非關鍵步驟(如截圖)使用 Skip,關鍵步驟(如資料寫入)使用 Abort,網路操作使用 Retry,彈性配置每個 State 的容錯行為。
版本紀錄
| 版本 | 日期 | 說明 |
|---|---|---|
| 1.0 | 2026-04-04 | 初版 |