简体   繁体   English

使用任务或线程从大数据表(进度数据库)逐行更新数据网格,但在更新期间保持 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

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM