BackgroundWorker 填充 DataGridView

Posted

技术标签:

【中文标题】BackgroundWorker 填充 DataGridView【英文标题】:BackgroundWorker to fill a DataGridView 【发布时间】:2019-11-02 06:09:01 【问题描述】:

编辑:使用此解决:http://reedcopsey.com/2011/11/28/launching-a-wpf-window-in-a-separate-thread-part-1/

在我的项目(.net/windows 表单)中,我正在用一个大的DataTable 填写DataGridView。填充可能需要 20 秒,所以我想要一个动画加载窗口。如果线程忙,此动画将冻结,因此我必须为窗口或填充 DataGridView 使用新线程。

我尝试使用BackgroundWorker 来显示表单,但它会是一个正确形状的空白窗口。

我也尝试使用BackgroundWorker 来填充DataGridView,但它会抛出一个错误,指出DataGridView 正在由与创建它的线程不同的线程访问。由于 DataGridView 是在设计器类中创建的,所以我不能只在新线程中创建它——而且该解决方案听起来不太优雅。

在填写DataGridView 时,对我来说,展示一个有效的动画表单的最佳方式是什么?

编辑:答案并没有为我解决问题,所以我尝试将代码分解为可以在此处呈现的内容。我以前没有这样做,因为它看起来并没有足够的相关性来处理大约 1k 行代码。此处提供的代码中可能缺少某些内容或先前实验的一些残留物。请忽略函数的错误命名,这是一个遗留问题,一旦我开始工作,我会修复它。部分代码是古老的。

我让它运行没有错误,但是 frmLoading 仍然没有动画(如果我在工作线程不忙的时候让它保持活动状态)。

namespace a

    public partial class frmMain : DockContent, IPlugin
    
        //...
        private delegate void SafeCallDelegate(DataTable dt);
        private Thread thread1 = null;
        private frmLoading frmLoading = new frmLoading();


        public frmMain()
        
            //...
        
        //...

        private void FillDataGrid(DataTable dt)
        
            if(this.InvokeRequired)
            
                var d = new SafeCallDelegate(FillDataGrid);
                Invoke(d, new object[]  dt );
            
            else
            
                //...
                DataGridFiller(dt);
            
        

        private void DataGridFiller(DataTable dt)
        
            BindingSource dataSource = new BindingSource(dt, null);
            //...
            dgvData.DataSource = dataSource;
            //...
            frmLoading.Hide();
        

        private void btnGetData_Click(object sender, EventArgs e)
        
            DataTable dt = [...];

            // Wenn Daten vorhanden sind, dann anzeigen
            if (dt != null)
            
                //...

                frmLoading.Show();
                thread1 = new Thread(() => FillDataGrid(dt));
                thread1.Start();
            
        
    

【问题讨论】:

查看 Microsoft 提供的任务并行库。它的 async/await 模式正是您所需要的。 我们需要看代码。 即使您的问题已经有了答案(使用 BackgroundWorker 和 Control.Invoke),在您的问题中提供代码示例总是有意义的。例如,如果慢速部分是一个 IO 调用,那么如果您的 .NET Framework 版本支持,自然会使用 async / await 进行此类调用。 是的,我刚才正在处理这个问题。以前没有这样做,因为它似乎真的没有必要(而且这是一堆工作),但是即使尝试了解决方案仍然存在问题。 @Infrisios 我编辑了我的答案,希望您现在可以解决您的问题。问候 【参考方案1】:

第二种方法是正确的:使用BackgroundWorker 来执行任何在主线程中完成时会冻结 UI 的工作。关于您遇到的异常,那是因为您正在进行的调用不是线程安全的(因为控件是在调用其方法的人的不同线程上创建的)。请查看this link to MSDN,了解如何使用 backgroundWorker 的事件驱动模型在线程之间进行这种调用。

来自链接:

有两种方法可以安全地从 没有创建该控件的线程。您可以使用 System.Windows.Forms.Control.Invoke 调用创建的委托的方法 在主线程中,它又调用控件。或者,您可以 实现一个System.ComponentModel.BackgroundWorker,它使用一个 事件驱动模型将后台线程中完成的工作与 报告结果。

看看第二个例子,它使用 backgroundWorker 演示了这种技术

编辑

从您的 cmets 中,我了解到这里真正的问题是尺寸。问题是拥有DataGridView 控件的线程是显示它的线程,所以无论如何,如果您一次加载所有数据,它将冻结该线程绘制所有数据所需的时间屏幕上。幸运的是,您不是第一个遇到此问题的人,这次微软为您解决了问题。我认为这是XY problem 的一个很好的例子,真正的问题是你想要加载一个巨大的数据集(X),而关于如何做到这一点的答案是here,但是你问的是如何在没有 UI 冻结 (Y) 的情况下填充数据网格时显示加载图标,这是您尝试的解决方案。

这是从技术角度来看的,但从 UI/UX 角度来看,我希望您考虑一下表单的实际使用情况。每次访问此部分时,您真的需要加载所有数据吗?如果答案是否定的,也许你可以考虑实现一些分页(例如here)。即使对于超宽显示器,200 列也意味着水平滚动。我真的无法弄清楚用户案例是否需要一次在列表/表格视图中显示所有这些信息。我认为实现Master-Detail 类型的接口可能会更有用。

编辑 2.0

我认为您可以尝试在另一个线程中创建一个全新的Form 窗口,将其放在您的数据网格上并在那里绘制加载动画。同时,在主窗口中,您可以在不绘制网格的情况下绘制网格,从而使加载动画冻结(另一个线程负责绘制)。您可能想要持有对线程的引用并将其终止,或者最好尝试持有对 From 的引用并更优雅地关闭它。

我从来没有这样做过,但是,我想我记得一些遗留应用程序做了类似的事情,而且很丑陋。

【讨论】:

谢谢!这帮助我运行它没有错误,但仍然没有按预期工作。请参阅上面的编辑。 感谢您的编辑,这解释了很多。快速概述发生的情况:客户为各种用途创建自定义查询,大多数是分页或通过 excel 导出的。这是他可以手动触发其中一个查询以获得完整概述的地方。不对这个特定视图进行分页而是设置加载窗口的任务来自上方。 那加载表单不应该有新的线程吗?另外,我在调试时检查了线程,它根本没有创建新线程,至少不应该这样做吗? 您尝试过虚拟模式吗?如链接的 SO 问题中所述,如果您想使用大型数据集,虚拟模式是可以遵循的路径。 它基本上是我提出的,但使用 WPF 而不是 winforms,对吧?不客气【参考方案2】:

使用异步任务显示弹出加载窗口,直到 DataGridView 被填满。

这是微软为异步编程撰写的一篇文章的链接:https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/

【讨论】:

异步任务将无法中断繁忙的 UI 线程。

以上是关于BackgroundWorker 填充 DataGridView的主要内容,如果未能解决你的问题,请参考以下文章

使用 C# Windows 窗体中 SQL 查询的大量结果填充 dataGridView

C# form发起backgroundworker 当form close时 backgroundworker 还会继续工作吗

将值从 BackgroundWorker DoWork 传递到 BackgroundWorker Completed

BackgroundWorker控件使用

C# BackGroundWorker backgroundWorker1_DoWork中,按钮不能按的问题

将 'BackgroundWorker' 替换为 'Thread'