使用任务或线程从大数据表(进度数据库)逐行更新数据网格,但在更新期间保持 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 响应的主要内容,如果未能解决你的问题,请参考以下文章
SQL中游标的使用--遍历数据逐行更新或删除:相当于for循环
C 语言文件操作 ( 配置文件读写 | 写出或更新配置文件 | 逐行遍历文件文本数据 | 获取文件中的文本行 | 查询文本行数据 | 追加文件数据 | 使用占位符方式拼接字符串 )