Excel 是 STA (Single Thread Apartment) 應用程式,增益集寄宿於 Excel 進程,同屬 STA。背景執行緒直接呼叫 Excel COM 物件 (如寫入儲存格、格式化) 在 Excel 忙碌時 (使用者操作、計算) 會拋出難以捕捉的 COM 例外 (RPC_E_SERVERCALL_RETRYLATER、VBA_E_IGNORE、RPC_E_CALLREJECTED),導致增益集不穩定。
SynchronizationContext 是執行緒間通訊抽象,UI 執行緒建立首個控件時自動綁定。WindowsFormsSynchronizationContext (內部用 PostMessage)DispatcherSynchronizationContextPost (非同步) / Send (同步) 將委派封送至目標執行緒佇列。
private void button2_Click(object sender, EventArgs e) {
var uiContext = SynchronizationContext.Current; // 擷取 UI 執行緒上下文
var thread = new Thread(Run);
thread.Start(uiContext); // 傳遞給背景執行緒
}
private void Run(object state) {
var uiContext = (SynchronizationContext)state;
for (int i = 0; i < 10; i++) {
Thread.Sleep(10); // 模擬耗時運算
uiContext.Post(UpdateUI, "line " + i); // 非同步封送至 UI 執行緒
}
}
private void UpdateUI(object state) {
comboBox1.Items.Add((string)state); // 安全執行於 UI 執行緒
}
輸出顯示 Button/UpdateUI 同執行緒 ID (9),Run 為背景執行緒 (10)。
增益集載入初期 SynchronizationContext.Current 為 null,需在載入完成後 (通常於登入視窗/自訂工作窗格建立時) 主動建立控件以綁定上下文。
public class MyRtdServer : IRtdServer
{
private SynchronizationContext _excelContext;
private IRTDUpdateEvent _callback;
public int ServerStart(IRTDUpdateEvent callback) {
_callback = callback;
// 必須在 Excel UI 執行緒呼叫此方法時擷取
_excelContext = new WindowsFormsSynchronizationContext(); // 或 SynchronizationContext.Current
return 1;
}
// 背景執行緒 (如行情推送) 觸發更新
public void OnMarketDataUpdate() {
// 錯誤做法:_callback.UpdateNotify(); // 直接呼叫 → COM 例外風險
// 正確做法:封送至 Excel UI 執行緒
_excelContext.Post(_ => _callback.UpdateNotify(), null);
}
// ... 其他介面方法
}
| 場景 | 錯誤做法 | 正確做法 |
|---|---|---|
| 背景執行緒寫入儲存格/格式化 | 直接呼叫 Range.Value = ... | 封裝為 Action,用 _excelContext.Post/Invoke 封送 |
| RTD 伺服器推送更新 | 背景執行緒直接 UpdateNotify() | _excelContext.Post(_ => _callback.UpdateNotify(), null) |
| UDF 函數異步計算 | 背景執行緒回呼 Excel API | 計算完成後封送結果至 UI 執行緒賦值 |
| 捕捉 COM 例外 | 嘗試 catch 0x8001010A 等錯誤碼重試 | **根治**:避免背景執行緒直接呼叫 COM,改用封送 |
Excel 增益集穩定性的核心在於:**所有 COM 物件呼叫必須在 Excel UI 執行緒 (STA) 執行**。透過在增益集載入階段擷取 SynchronizationContext,背景執行緒僅負責運算,將 COM 操作封送至 UI 執行緒,可徹底消除時序性 COM 例外,大幅提升增益集健壯度。