核心組件
- ScriptManager:AJAX 基礎設施,管理部分更新、腳本註冊
- UpdatePanel:定義部分更新區域,包裹需定時刷新的內容
- Timer:定時觸發 PostBack,Interval 單位毫秒
前端完整代碼
後台代碼
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
InitializeData(); // 首次加載
}
}
protected void Timer1_Tick(object sender, EventArgs e)
{
// 1. 執行定時任務邏輯
RefreshRealTimeData();
// 2. 可選:客戶端腳本通知 (如彈窗、聲音)
ScriptManager.RegisterStartupScript(this, GetType(), "notify",
"console.log('定時刷新完成: " + DateTime.Now + "');", true);
}
private void InitializeData()
{
lblServerTime.Text = "服務器時間: " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
gvRealTimeData.DataSource = GetRealTimeData();
gvRealTimeData.DataBind();
}
private void RefreshRealTimeData()
{
lblServerTime.Text = "服務器時間: " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
gvRealTimeData.DataSource = GetRealTimeData();
gvRealTimeData.DataBind();
// UpdatePanel 自動處理部分更新渲染
}
private DataTable GetRealTimeData()
{
// 模擬數據庫查詢
DataTable dt = new DataTable();
dt.Columns.Add("ID", typeof(int));
dt.Columns.Add("Name", typeof(string));
dt.Columns.Add("Value", typeof(double));
dt.Columns.Add("UpdateTime", typeof(DateTime));
Random r = new Random();
for (int i = 1; i <= 10; i++)
{
dt.Rows.Add(i, $"項目{i}", Math.Round(r.NextDouble() * 100, 2), DateTime.Now);
}
return dt;
}
關鍵屬性配置
| 屬性 | 推薦值 | 說明 |
| Timer.Interval | 30000-300000 | 30秒-5分鐘,避免過於頻繁請求 |
| UpdatePanel.UpdateMode | Conditional | 手動控制更新,性能更好 |
| UpdatePanel.ChildrenAsTriggers | true | 內部控件觸發更新 |
| ScriptManager.EnablePartialRendering | true | 啟用部分渲染 (默認) |
進階優化技巧
1. 動態調整間隔
protected void Timer1_Tick(object sender, EventArgs e)
{
// 業務高峰期縮短間隔,閒時延長
if (IsPeakHours())
Timer1.Interval = 30000; // 30秒
else
Timer1.Interval = 300000; // 5分鐘
RefreshData();
}
2. 條件式更新 (減少不必要渲染)
protected void Timer1_Tick(object sender, EventArgs e)
{
var newData = GetRealTimeData();
if (HasDataChanged(newData)) // 對比邏輯
{
gvRealTimeData.DataSource = newData;
gvRealTimeData.DataBind();
UpdatePanel1.Update(); // 手動觸發更新
}
// 數據未變不更新,節省帶寬
}
3. 錯誤處理與恢復
protected void Timer1_Tick(object sender, EventArgs e)
{
try
{
RefreshRealTimeData();
}
catch (Exception ex)
{
LogError(ex);
// 可選:增加間隔避免錯誤風暴
Timer1.Interval = Math.Min(Timer1.Interval * 2, 300000);
ScriptManager.RegisterStartupScript(this, GetType(), "error",
"showToast('數據刷新失敗,將稍後重試');", true);
}
}
常見問題排查
| 問題 | 原因 | 解決 |
| 全頁刷新 | Timer 不在 UpdatePanel 內 / ScriptManager 缺失 | 確保 Timer 在 UpdatePanel.ContentTemplate 內 |
| 定時器不觸發 | Interval=0 / Enabled=false / JS 錯誤阻塞 | 檢查瀏覽器控制台錯誤,確保 Interval > 0 |
| Session 丟失 | 長時間輪詢導致 Session 過期 | 增加 Session Timeout / 使用無狀態 Token |
| 並發衝突 | 多用戶同時觸發寫操作 | Timer 僅用於讀取,寫操作用按鈕觸發 |
現代化替代方案
| 方案 | 技術棧 | 優勢 |
| SignalR | WebSocket / Server-Sent Events | 服務器主動推送、真正實時、雙向通信 |
| JavaScript setInterval + Fetch | 純前端輪詢 | 無 ViewState 開銷、輕量、可控 |
| Blazor Timer | WebAssembly / Server | 組件化、狀態管理、現代語法 |
| Hangfire / Quartz | 後台作業調度 | 持久化、分佈式、可靠、非請求響應型 |
SignalR 示例 (推薦新項目)
// Hub
public class DashboardHub : Hub
{
public async Task SendRealTimeData() =>
await Clients.All.SendAsync("ReceiveData", GetData());
}
// 後台服務定時推送
public class RealTimeDataService : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
await _hubContext.Clients.All.SendAsync("ReceiveData", GetData());
await Task.Delay(30000, stoppingToken);
}
}
}
// 前端
const connection = new signalR.HubConnectionBuilder()
.withUrl("/dashboardHub").build();
connection.on("ReceiveData", (data) => updateUI(data));
await connection.start();
Timer + UpdatePanel 是 WebForms 時代的經典方案,理解其機制有助於維護舊系統,新項目強烈建議採用 SignalR 或前端輪詢實現真正的實時體驗。