多线程环境下更新DataGridView绑定的DataTable及界面刷新方法
多线程更新DataTable并同步DataGridView的问题解决
问题场景
我有Form1和Form2两个窗体,Form1中存在一个DataTable,Form2的DataGridView绑定了该DataTable。使用Task.Factory.StartNew在Form1中更新DataTable时遇到以下问题:
- 向DataTable添加新行后,DataGridView需滚动才会更新显示;
- 清空DataTable再添加新行后,DataGridView无变化,点击列排序时还会报错。
咨询两个问题:
- 多线程环境下更新DataGridView绑定的DataTable的正确方式是什么?
- 更新DataTable后如何刷新DataGridView使其重新加载数据?
原代码
Form1
using System.ComponentModel; using System.Data; using System.Diagnostics; namespace dgvTest { public partial class Form1 : Form { // DataTable for trade histories public static DataTable? TradeHistoryDT = null; public Form1() { InitializeComponent(); TradeHistoryDT = new DataTable(); TradeHistoryDT.TableName = "TradeHistory"; TradeHistoryDT.Columns.Add("Time", typeof(DateTime)); TradeHistoryDT.Columns.Add("Side", typeof(string)); TradeHistoryDT.Columns.Add("Ticker", typeof(string)); TradeHistoryDT.Columns.Add("Price", typeof(int)); TradeHistoryDT.Columns.Add("Quantity", typeof(int)); } private void button1_Click(object sender, EventArgs e) { Form2 form = new Form2(); form.Show(); } public void add_rows_to_tt(int n) { List<Trade> new_trades = GenerateRandomTrades(n); ListToDT(new_trades); } public void change_whole_data(int n) { TradeHistoryDT.Clear(); List<Trade> new_trades = GenerateRandomTrades(n); ListToDT(new_trades); } public void ListToDT(List<Trade> new_trades) { foreach (Trade t in new_trades) { TradeHistoryDT.Rows.Add(t.ExecuteTime, t.Side, t.Ticker, t.Price, t.Quantity); } } public static List<Trade> GenerateRandomTrades(int numberOfTrades) { Random random = new Random(); List<Trade> trades = new List<Trade>(); for (int i = 0; i < numberOfTrades; i++) { Trade trade = new Trade { ExecuteTime = DateTime.Now.AddMinutes(-1 * random.Next(1, 100)), Side = random.Next(2) == 0 ? "Buy" : "Sell", Ticker = (TickerList)random.Next(1, Enum.GetValues(typeof(TickerList)).Length), Price = random.Next(9000, 11000), Quantity = random.Next(1, 50) }; trades.Add(trade); } return trades; } private void button2_Click(object sender, EventArgs e) { int n = int.Parse(textBox1.Text); Task.Factory.StartNew(() => add_rows_to_tt(n)); } private void button3_Click(object sender, EventArgs e) { int n = int.Parse(textBox1.Text); Task.Factory.StartNew(() => change_whole_data(n)); } } }
Form2
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace dgvTest { public partial class Form2 : Form { public Form2() { InitializeComponent(); dataGridView1.DataSource = Form1.TradeHistoryDT; } } }
解决方案
一、多线程环境下更新DataTable的正确方式
WinForm控件仅允许在UI线程中访问和更新,直接在后台线程修改绑定的DataTable会触发UI同步异常——因为DataTable的变更通知会直接触发DataGridView的UI更新,而后台线程无权操作UI控件。正确流程:
- 后台线程只负责数据生成/获取,不直接修改绑定的DataTable;
- 通过
Control.Invoke或Control.BeginInvoke将数据更新操作切换到UI线程执行; - 推荐用
BindingSource作为DataTable和DataGridView的中间层,它能更稳定地处理数据变更通知,减少同步问题。
二、更新DataTable后刷新DataGridView的方法
使用BindingSource时,数据变更会自动同步到DataGridView;若直接绑定DataTable,需确保变更操作在UI线程执行,或手动调用DataGridView.Refresh()。执行Clear()再添加行的操作后,可调用BindingSource.ResetBindings(false)强制触发UI刷新,避免通知丢失。
修改后的代码示例
Form1 修改版
using System.ComponentModel; using System.Data; using System.Diagnostics; namespace dgvTest { public partial class Form1 : Form { // 用BindingSource作为数据中间层,提升同步稳定性 public static BindingSource TradeHistoryBS = new BindingSource(); private DataTable? TradeHistoryDT = null; public Form1() { InitializeComponent(); TradeHistoryDT = new DataTable(); TradeHistoryDT.TableName = "TradeHistory"; TradeHistoryDT.Columns.Add("Time", typeof(DateTime)); TradeHistoryDT.Columns.Add("Side", typeof(string)); TradeHistoryDT.Columns.Add("Ticker", typeof(string)); TradeHistoryDT.Columns.Add("Price", typeof(int)); TradeHistoryDT.Columns.Add("Quantity", typeof(int)); TradeHistoryBS.DataSource = TradeHistoryDT; } private void button1_Click(object sender, EventArgs e) { Form2 form = new Form2(); form.Show(); } public void add_rows_to_tt(int n) { // 后台线程仅生成数据,不操作DataTable List<Trade> new_trades = GenerateRandomTrades(n); // 切换到UI线程更新数据 this.Invoke((Action)(() => { ListToDT(new_trades); })); } public void change_whole_data(int n) { List<Trade> new_trades = GenerateRandomTrades(n); // 清空和添加行必须在UI线程执行 this.Invoke((Action)(() => { TradeHistoryDT.Clear(); ListToDT(new_trades); // 强制触发BindingSource的变更通知 TradeHistoryBS.ResetBindings(false); })); } public void ListToDT(List<Trade> new_trades) { foreach (Trade t in new_trades) { TradeHistoryDT.Rows.Add(t.ExecuteTime, t.Side, t.Ticker, t.Price, t.Quantity); } } public static List<Trade> GenerateRandomTrades(int numberOfTrades) { Random random = new Random(); List<Trade> trades = new List<Trade>(); for (int i = 0; i < numberOfTrades; i++) { Trade trade = new Trade { ExecuteTime = DateTime.Now.AddMinutes(-1 * random.Next(1, 100)), Side = random.Next(2) == 0 ? "Buy" : "Sell", Ticker = (TickerList)random.Next(1, Enum.GetValues(typeof(TickerList)).Length), Price = random.Next(9000, 11000), Quantity = random.Next(1, 50) }; trades.Add(trade); } return trades; } private void button2_Click(object sender, EventArgs e) { int n = int.Parse(textBox1.Text); Task.Factory.StartNew(() => add_rows_to_tt(n)); } private void button3_Click(object sender, EventArgs e) { int n = int.Parse(textBox1.Text); Task.Factory.StartNew(() => change_whole_data(n)); } } }
Form2 修改版
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace dgvTest { public partial class Form2 : Form { public Form2() { InitializeComponent(); // 绑定到BindingSource而非直接绑定DataTable dataGridView1.DataSource = Form1.TradeHistoryBS; } } }
关键修改说明
- 引入BindingSource:作为数据层和UI层的中间件,优化变更通知机制,避免直接绑定DataTable的同步延迟;
- 线程切换:所有修改DataTable的操作都通过
this.Invoke切换到UI线程,规避跨线程操作UI的异常; - ResetBindings:清空表并添加新行后,调用该方法强制刷新DataGridView,确保数据变更被正确感知。
内容的提问来源于stack exchange,提问作者runRyan
相关产品推荐
相关产品推荐

