You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

多线程环境下更新DataGridView绑定的DataTable及界面刷新方法

多线程更新DataTable并同步DataGridView的问题解决

问题场景

我有Form1和Form2两个窗体,Form1中存在一个DataTable,Form2的DataGridView绑定了该DataTable。使用Task.Factory.StartNew在Form1中更新DataTable时遇到以下问题:

  • 向DataTable添加新行后,DataGridView需滚动才会更新显示;
  • 清空DataTable再添加新行后,DataGridView无变化,点击列排序时还会报错。

咨询两个问题:

  1. 多线程环境下更新DataGridView绑定的DataTable的正确方式是什么?
  2. 更新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;
        }
    }
}

关键修改说明

  1. 引入BindingSource:作为数据层和UI层的中间件,优化变更通知机制,避免直接绑定DataTable的同步延迟;
  2. 线程切换:所有修改DataTable的操作都通过this.Invoke切换到UI线程,规避跨线程操作UI的异常;
  3. ResetBindings:清空表并添加新行后,调用该方法强制刷新DataGridView,确保数据变更被正确感知。

内容的提问来源于stack exchange,提问作者runRyan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 08:07:55