【问题标题】:Nonblocking update to a DataGridView对 DataGridView 的非阻塞更新
【发布时间】:2016-03-19 00:57:21
【问题描述】:

我了解如何使用委托来更新主控制线程上的控件,就像一个魅力。我的问题是,如果我向绑定DataGridView 添加一个大的DataSet(比如2000 个项目),填充网格需要5-8 秒,并且在这5-8 秒内整个GUI 被锁定。如何更新DataGridView 使其不锁定用户界面?

需要明确的是,问题不在于我正在对数据库进行慢速查询并且 UI 对此进行了阻塞,我已经有了 DataSet object[] 并将对象数组添加到 BindingList<object>DataGrid 必然如此:

BindingList<object> dataProvider = new BindingList<object>();
DataGridView gridView = new DataGridView();
gridView.DataSource = dataProvider;

// ...stuff happens...

object[] source = dataSet; //of 2000 items
foreach (object item in source) {  //this foreach blocks
    dataProvider.Add(item);
}

我尝试了各种方法(我知道这行不通,但我想我会看到),比如创建一个执行 dataProvider.Add() 的委托,但这并不重要,因为它仍然必须在控制线程上发生。

一些好的建议围绕着首先构建BindingList,然后设置gridView.DataSource。虽然这有效(它会立即更新网格),但我看到添加更多数据的唯一方法是创建另一个新的BindingList,执行gridView.DataSource.copyTo()(获取现有数据)并在其上添加新数据,然后将gridView.DataSource 设置为新的BindingList。这对我不起作用,因为我列表中的对象不是静态的,它们每个都将数据异步上传到服务器,并将它们复制到新的 BindingList 会导致问题。

【问题讨论】:

    标签: c# multithreading datagridview delegates nonblocking


    【解决方案1】:

    就像尼克说的那样,BackgroundWorker 可能是你最好的选择。

    这是一个非常简单的例子

    public partial class Form1 : Form
    {
        BackgroundWorker b = new BackgroundWorker();
    
        public Form1()
        {
            InitializeComponent();
            b.RunWorkerCompleted += new RunWorkerCompletedEventHandler(b_RunWorkerCompleted);
            b.DoWork += new DoWorkEventHandler(b_DoWork);
        }
    
        void b_DoWork(object sender, DoWorkEventArgs e)
        {
            // build dataset here and assigning it to results
            e.Result = dataset;            
        }
    
        void b_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            // assign the dataset you built in DoWork in the gridview and update it
            dataGridView1.DataSource = e.Result;
            dataGridView1.Update();
        }
    
        private void button1_Click(object sender, EventArgs e)
        {
            b.RunWorkerAsync();
        }
    }
    

    【讨论】:

    • 这是另一个很好的建议,但它涉及每次我添加更多数据而不是向现有数据源添加更多数据时替换数据源。这是一个问题,因为 dataSource 中的对象正在将数据上传到服务器,所以我不能只做 BindingList.copyTo() 因为每个对象都不是静态的。
    • Shizam,你试过这个吗?只要元素是类(不是结构)就应该没有问题。
    【解决方案2】:

    当 GridView 链接到 DataSource 时,您正在添加记录。这意味着它每次都会更新布局。

    如何先填充 DataSource,然后再设置 DataSource 属性?

    gridView.DataSource = null;
    ...stuff happens...
    
    object[] source = dataSet; //of 2000 items
    foreach (object item in source) {  //this foreach blocks
        dataProvider.Add(item);
    }
    
    gridView.DataSource = dataProvider;
    

    foreach 循环可以转到另一个线程,但我认为您不需要它。

    【讨论】:

    • foreach 循环应该转到另一个线程,但这仍然是正确的想法。
    • 这取决于一点,但添加 2000 项对于正常事件通常是可以接受的。我猜他们已经在记忆中了。
    • 我尝试将 foreach 循环放在不同的线程中,但这无济于事,因为每个 .Add 仍然必须在控制线程上发生,这意味着整个 foreach 仍在控制线程上发生。 Henk 是对的,如果我将所有项目添加到 BindingList,然后将 gridView.DataSource 设置为 BindingList,它会立即更新。这里的问题是,如果我想向 gridView 添加更多项目,我必须创建一个新的 BindingList,将所有现有项目复制到其中并添加更多项目。这会破坏我在当前数据上运行的任何操作(感觉就像是 hack)。
    • 看,列表是上传文件的一堆对象,将它们复制到新列表会中断正在进行的上传并对上传线程产生其他不良影响。
    • 即使它分批发生,您也可以将 DataSource 属性设为空,运行循环,再次填充该属性。填充GUI部分不适合放在线程上。
    【解决方案3】:

    考虑使用 BackgroundWorker。我自己对它们了解不多,但在 Google 上稍作搜索表明这是一个很好的可能性。

    【讨论】:

      猜你喜欢
      • 2016-07-06
      • 1970-01-01
      • 2011-02-19
      • 1970-01-01
      • 2015-11-27
      • 1970-01-01
      • 2012-11-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多