使用任务或线程从大数据表(进度数据库)逐行更新数据网格,但在更新期间保持 UI 响应

Posted

技术标签:

【中文标题】使用任务或线程从大数据表(进度数据库)逐行更新数据网格,但在更新期间保持 UI 响应【英文标题】:Update datagrid row by row from a big data table (Progress database), using a task or thread, but keeping the UI responsive during the update 【发布时间】:2021-04-27 06:29:27 【问题描述】:

有一个包含 40-50.000 条记录的数据表。我通过 ODBC 连接到它,因为这是我被允许的唯一方法。 下面的代码是一个小项目,我想在其中测试用于逐行查看记录的解决方案,但在查询期间 UI 不会冻结。代码现在或多或少可以工作。程序打开,当我单击按钮时,UI 冻结,并且在每条记录处,都会自行更新,以便用户可以看到文章。但是,在查询完成之前,他们无法控制 UI。 有人可以帮忙解决吗?我很难阅读文档和收听有关任务的视频。

using System;
using System.Data.Odbc;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace AsyncSQLtest

    public partial class MainFrom : Form
    
        DataPresenter presenter;

        public MainFrom()
        
            InitializeComponent();
            presenter = new DataPresenter(this);
        

        private async void LoadData(object sender, EventArgs e)
             => await presenter.QueryDataAsync(this.dataGridView1);
    

    internal class DataPresenter
    
        private MainFrom mainFrom;

        public DataPresenter(MainFrom mainFrom)
               => this.mainFrom = mainFrom;

        internal async Task QueryDataAsync(DataGridView dgv)
        
            OdbcConnection con = new OdbcConnection("DSN=PA;UID=XXX;PWD=YYYYYYYYY");
            OdbcCommand cmd = new OdbcCommand("select Artikel from Table", con);

            dgv.Columns.Clear();
            dgv.Columns.Add("artikel", "artikel");

            try
            
                con.Open();
                var reader = await cmd.ExecuteReaderAsync();
                while (await reader.ReadAsync())
                
                    dgv.Invoke(new Action(
                        () =>
                        
                            dgv.Rows.Add(reader.GetString(0));
                            dgv.Refresh();
                        
                        ));
                

                reader.Close();
                con.Close();
                cmd.Dispose();
            
            catch (Exception e)
            
                reader.Close();
                con.Close();
                if (cmd != null) cmd.Dispose();
                MessageBox.Show(e.Message);
            
        
    

【问题讨论】:

【参考方案1】:

OdbcCommand 类的ExecuteReaderAsync 很可能具有DbCommand 基类提供的默认同步 实现。这意味着await reader.ReadAsync() 也很可能是100% synchronous。您可以通过使用Task.Run 方法将同步调用卸载到后台线程来解决此问题:

var reader = await Task.Run(() => cmd.ExecuteReader());
while (await Task.Run(() => reader.Read()))

dgv.Invoke(new Action( 可能是多余的,因为 QueryDataAsync 是从 UI 线程调用的。

将数据检索代码与 UI 操作代码混合可能不是一个好主意,因为它会导致应用层之间的紧密耦合。如果您使用的是 C# 8 或更高版本,您可以考虑以 asynchronous stream (IAsyncEnumerable<string>) 的形式公开数据:

internal async IAsyncEnumerable<string> QueryDataAsync()

    //...
    con.Open();
    var reader = await Task.Run(() => cmd.ExecuteReader());
    while (await Task.Run(() => reader.Read()))
    
        yield return reader.GetString(0);
    
    //...

...并像这样从表示层使用它:

private async void LoadData(object sender, EventArgs e)

    await foreach (string value in presenter.QueryDataAsync())
    
        dgv.Rows.Add(value);
    

由于编译错误,它仍然会很棘手:

错误 CS1626 无法在带有 catch 子句的 try 块的主体中​​产生值

您可能必须将catch 替换为finally,并让消费者处理错误。

【讨论】:

谢谢!太神奇了...:DI 昨天只是想如果我可以用 IEnumerable 和 yield return 以某种方式做到这一点,但无法找到它如何...这是最好的解决方案,就像我想象的那样工作!【参考方案2】:

您不需要使用Invoke,因为DataGridView 中的插入发生在主线程中。只有对数据库的查询是异步发生的。

代码可以简化如下

internal async Task QueryDataAsync(DataGridView dgv)

    try
    
        dgv.Columns.Clear();
        dgv.Columns.Add("artikel", "artikel");

        using (var con = new OdbcConnection("..."))
        using (var cmd = new OdbcCommand("select Artikel from Table", con))
        
            await con.OpenAsync();

            using (var reader = await cmd.ExecuteReaderAsync())
            
                while (await reader.ReadAsync())
                
                    dgv.Rows.Add(reader.GetString(0));
                
            
        
    
    catch (Exception e)
    
        MessageBox.Show(e.Message);
    

使用虚拟模式会好很多:walkthrough

【讨论】:

以上是关于使用任务或线程从大数据表(进度数据库)逐行更新数据网格,但在更新期间保持 UI 响应的主要内容,如果未能解决你的问题,请参考以下文章

多线程任务更新 1 个进度条 - UI C# WPF

SQL中游标的使用--遍历数据逐行更新或删除:相当于for循环

具有线程或多处理的 Tkinter CPU 密集型任务

C 语言文件操作 ( 配置文件读写 | 写出或更新配置文件 | 逐行遍历文件文本数据 | 获取文件中的文本行 | 查询文本行数据 | 追加文件数据 | 使用占位符方式拼接字符串 )

如何使用 pyserial 将文件逐行写入 com0com?

c# datagridview 绑定数据时逐行显示并且有进度条。问题如下: