| 維度 | 主執行緒 (UI Thread) | 背景執行緒 (ThreadPool/Async Callback) |
| 職責 | 頁面渲染、控制項資料繫結、ViewState 管理 | 非同步 I/O、長時間運算、WebService 回調 |
| 存取 UI 控制項 | 安全、同步 | 不安全、競態條件、狀態損壞 |
| 資料繫結操作 | 正常 | 嘗試修改 GridView/DataList 等繫結控制項 → **例外/靜默失敗** |
錯誤鏈路重現
- WebService 非同步完成 → 執行緒池執行緒執行回調委派
- 回調代碼嘗試 `GridView.DataSource = ...; GridView.DataBind()`
- 控制項處於「頁面生命週期已結束」或「另一請求正在渲染」狀態
- 繫結引擎偵測到跨執行緒存取 → 拋出 `HttpException: DataBinding 失敗` / `InvalidOperationException`
- 例外未捕獲 → 冒泡至 ASP.NET 執行階段 → 觸發 `w3wp.exe` 健康監測機制 → **進程回收**
微軟官方文檔佐證
「背景執行緒不直接與應用程式 UI 互動……若背景執行緒嘗試修改 UI 控制項,該控制項可能進入未知狀態,導致難以診斷的問題。例如動態產生的位圖無法顯示、資料集刷新時繫結控制項顯示衝突資訊。」
正確模式:非同步資料繫結的三種可行路徑
路徑 A:AJAX/UpdatePanel 部分頁面更新 (最常用)
- 前端發起非同步請求 → 後端同步取得資料 → 回傳 JSON/HTML 片段 → 前端 JS 更新 DOM
- 完全避開伺服器端跨執行緒 UI 操作
路徑 B:非同步頁面 (PageAsyncTask) + 同步繫結
protected void Page_Load(object sender, EventArgs e) {
PageAsyncTask task = new PageAsyncTask(
BeginGetData, EndGetData, TimeoutCallback, null);
RegisterAsyncTask(task);
}
IAsyncResult BeginGetData(object sender, EventArgs e, AsyncCallback cb, object state) {
return webService.BeginGetData(cb, state);
}
void EndGetData(IAsyncResult ar) {
DataTable dt = webService.EndGetData(ar);
// **關鍵:EndGetData 仍在主執行緒執行,可安全繫結**
GridView1.DataSource = dt;
GridView1.DataBind();
}
路徑 C:背景執行緒僅負責取資料,主執行緒負責繫結 (生產者/消費者)
- 回調執行緒:取得資料 → 序列化存入 `Session`/`Cache`/靜態 `ConcurrentQueue` → 設定 `ManualResetEvent` 通知
- 主執行緒 (或 `Page_PreRender`):等待通知 → 從共享存儲取資料 → `DataBind()`
- 需妥善處理逾時、並發、Session 狀態序列化等細節
總結與檢核清單
- ✅ **鐵律**:任何涉及 `Control.DataBind()`、`Control.Visible`、`Control.Text` 等 UI 狀態變更的代碼,**必須在主執行緒執行**
- ✅ 非同步回調/執行緒池/Timer/Delegate `BeginInvoke` **嚴禁直接操作 UI 控制項**
- ✅ 採用 `PageAsyncTask`、AJAX、或生產者/消費者模式分離「取數」與「繫結」
- ✅ 監控 `w3wp.exe` 回收事件 (System Event Log, Source: WAS),頻繁回收 = 未處理例外 = 代碼缺陷