[英]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
There is a datatable containing 40-50.000 records in it.有一个包含 40-50.000 条记录的数据表。 I'm connecting to it via ODBC because that's the only way I'm allowed to.
我通过 ODBC 连接到它,因为这是我被允许的唯一方法。 The code below is a little project in which I want to test out a solution for viewing the records row by row, but in a way that the UI isn't frozen during the query.
下面的代码是一个小项目,我想在其中测试用于逐行查看记录的解决方案,但在查询期间 UI 不会冻结。 The code now works, more or less.
代码现在或多或少可以工作。 The program opens, and when I click on the button, the UI freezes and at every record, updates itself, so that the user can see the articles.
程序打开,当我单击按钮时,UI 冻结,并且在每条记录处,都会自行更新,以便用户可以看到文章。 Though, they cannot controls the UI until the query is finished.
但是,在查询完成之前,他们无法控制 UI。 Can somebody help to solve it?
有人可以帮忙解决吗? I had a hard time reading documentations and listening to videos about tasks.
我很难阅读文档和收听有关任务的视频。
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);
}
}
}
}
You do not need to use Invoke
, since the insertion in the DataGridView
occurs in the main thread.您不需要使用
Invoke
,因为DataGridView
中的插入发生在主线程中。 Only the query to the database occurs asynchronously.只有对数据库的查询是异步发生的。
The code can be simplified as follows代码可以简化如下
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);
}
}
It will be much better to use virtual mode: walkthrough使用虚拟模式会好得多: 演练
The ExecuteReaderAsync
of the OdbcCommand
class most probably has the default synchronous implementation provided by the DbCommand
base class. OdbcCommand
class 的ExecuteReaderAsync
很可能具有由DbCommand
基础 class 提供的默认同步实现。 This means that also the await reader.ReadAsync()
is most probably 100% synchronous .这意味着
await reader.ReadAsync()
也很可能是100% 同步的。 You could fix this problem by offloading the synchronous call to a background thread, using the Task.Run
method:您可以通过使用
Task.Run
方法将同步调用卸载到后台线程来解决此问题:
var reader = await Task.Run(() => cmd.ExecuteReader());
while (await Task.Run(() => reader.Read()))
{
The dgv.Invoke(new Action(
is probably redundant, because the QueryDataAsync
is called from the UI thread. dgv.Invoke(new Action(
可能是多余的,因为QueryDataAsync
是从 UI 线程调用的。
Mixing data retrieval code with UI manipulation code is probably not a good idea, because it results to tight coupling between the application layers.将数据检索代码与 UI 操作代码混合可能不是一个好主意,因为它会导致应用程序层之间的紧密耦合。 If you are using C# 8 or later you could consider exposing the data in the form of an asynchronous stream (
IAsyncEnumerable<string>
):如果您使用的是 C# 8 或更高版本,您可以考虑以异步 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);
}
//...
}
...and consume it from the presentation layer like this: ...并从表示层使用它,如下所示:
private async void LoadData(object sender, EventArgs e)
{
await foreach (string value in presenter.QueryDataAsync())
{
dgv.Rows.Add(value);
}
}
It would still be tricky because of the compile error:由于编译错误,它仍然很棘手:
Error CS1626 Cannot yield a value in the body of a try block with a catch clause
错误 CS1626 无法在带有 catch 子句的 try 块的主体中产生值
You would probably have to replace the catch
with a finally
, and have the consumer handle the errors.您可能必须将
catch
替换为finally
,并让使用者处理错误。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.