跳到主要内容

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

套件版本用途
Stateless5.20.1有限狀態機核心引擎
System.Text.Json8.0.5JSON 序列化
Microsoft.Extensions.DependencyInjection8.0.1DI 容器抽象

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 觸發;終止時自動進入 CompletedError

各狀態的可觀察行為:

狀態CurrentStateName可呼叫的 API轉出條件
IdlenullExecuteAsync呼叫 ExecuteAsync → Running
Runningworkflow 內部 state 名稱PauseAsync、自然完成PauseAsync → Paused;正常完成 → Completed;例外 → Error
Paused暫停時的 state 名稱ResumeAsyncResumeAsync → Running
Completed最後執行的 state 名稱ExecuteAsync(新 workflow)重新呼叫 ExecuteAsync → Running
Error失敗時的 state 名稱ExecuteAsync(新 workflow)重新呼叫 ExecuteAsync → Running

兩個容易混淆的狀態:

  • RpaEngineState.Running 期間,內部會在多個 RpaState.Name 之間切換——StateChanged 事件追蹤的是 workflow 內部 state,引擎本身仍維持 Running
  • Error 不是終止狀態——可以重新呼叫 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 的執行順序為:EntryActionHandlerExitAction。若任一步驟拋出例外,依 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
}
策略行為適用場景
RetryMaxRetryCountRetryDelay 重試,超過次數拋出 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 元素掃描錯誤

各例外重點屬性

例外類別額外屬性說明
WorkflowExceptionstring? StateName發生錯誤的 State 名稱
ElementNotFoundExceptionstring Identifier搜尋用的元素識別字串
ElementNotInteractableExceptionstring ElementName, string Reason無法互動的元素與原因
LicenseExpiredExceptionDateTime? ExpirationDate授權到期日
FeatureNotLicensedExceptionLicenseFeature Feature未授權的功能旗標

使用範例

以下示範一個 3 狀態工作流程:InitProcessComplete,包含 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-safeStateMachine<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:

  1. ExecuteAsync 主迴圈在每次狀態執行前 await _pauseSignal.Task
  2. PauseAsync() 建立新的未完成 TaskCompletionSource,主迴圈自然阻塞
  3. ResumeAsync() 呼叫 TrySetResult() 解除阻塞

此設計確保:

  • 暫停發生在 State 之間,不會中斷正在執行的 Handler
  • 使用 volatile 標記 _engineState,保證跨執行緒的可見性
  • 搭配 CancellationToken 支援取消,避免永久掛起

錯誤復原策略

每個 RpaState 可獨立覆寫 workflow 層級的 DefaultErrorPolicy

  • State 層級優先state.ErrorRecovery ?? workflow.DefaultErrorPolicy
  • Retry 含延遲:透過 MaxRetryCountRetryDelay 控制重試行為
  • Skip 記錄日誌:跳過時自動寫入 ExecutionLog,不影響後續 State
  • Abort 拋出 WorkflowException:附帶 StateName 以利偵錯

這讓開發者可以在非關鍵步驟(如截圖)使用 Skip,關鍵步驟(如資料寫入)使用 Abort,網路操作使用 Retry,彈性配置每個 State 的容錯行為。

版本紀錄

版本日期說明
1.02026-04-04初版