使用 Timer 輪詢方式每秒重新綁定 DataSource 到 DataGridView,會導致 UI 假死、閃爍、記憶體飆升。需從架構層面優化。
// 這會導致嚴重效能問題
private void timer_Tick(object sender, EventArgs e) {
DataTable dt = GetDataFromDB(); // 查詢資料庫
dataGridView1.DataSource = dt; // 完整重綁 -> UI 重繪、閃爍、假死
dataGridView1.Refresh();
}
// 使用 BindingList 配合 INotifyPropertyChanged
public class StockItem : INotifyPropertyChanged {
private string _symbol;
private decimal _price;
public string Symbol { get => _symbol; set { _symbol = value; OnPropertyChanged(); } }
public decimal Price { get => _price; set { _price = value; OnPropertyChanged(); } }
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
// 表單載入時一次綁定
private BindingList _data = new BindingList();
dataGridView1.DataSource = _data;
// 背景執行緒只更新屬性值,UI 自動反映
Task.Run(async () => {
while (running) {
var updates = await GetLatestPricesAsync();
foreach (var u in updates) {
var item = _data.FirstOrDefault(x => x.Symbol == u.Symbol);
if (item != null) item.Price = u.Price; // 觸發 UI 更新
}
await Task.Delay(1000);
}
});
dataGridView1.VirtualMode = true;
dataGridView1.RowCount = totalCount;
// 實作 CellValueNeeded 事件
private void dataGridView1_CellValueNeeded(object sender, DataGridViewCellValueEventArgs e) {
e.Value = GetCellData(e.RowIndex, e.ColumnIndex); // 按需載入
}
// 資料變更時只重繪受影響列
dataGridView1.InvalidateRow(rowIndex);
private async void RefreshDataAsync() {
var newData = await Task.Run(() => GetDataFromDB()); // 背景查詢
// UI 執行緒批次更新
dataGridView1.SuspendLayout();
dataGridView1.Rows.Clear();
foreach (var row in newData) {
dataGridView1.Rows.Add(row.ItemArray);
}
dataGridView1.ResumeLayout();
}
| 問題 | 原因 | 解決方案 |
|---|---|---|
| UI 假死 | UI 執行緒阻塞於資料庫查詢 | 非同步查詢 |
| 閃爍 | 完整重設 DataSource 觸發完整重繪 | 增量更新 / VirtualMode |
| 記憶體暴增 | 重複建立 DataTable、未釋放 | 重複使用集合、實作 IDisposable |
| 選取遺失 | 重綁定重置選取狀態 | 保存/恢復 CurrentCell、SelectedRows |
核心心法:避免輪詢、避免完整重綁、避免 UI 執行緒做 I/O。採用資料繫結通知機制或虛擬模式,才能流暢處理即時資料。