【问题标题】:Progressbar on Waitform freezes using a Task run on another ThreadWaitform 上的进度条使用在另一个线程上运行的任务冻结
【发布时间】:2019-05-07 07:16:20
【问题描述】:

我有一个使用 OleDb 从 Excel 表格中获取数据的应用程序。
在表单上,​​我有控件,因此用户可以根据自己的需要过滤数据。
例如,FileSizeUserIDRootpath 等。这非常有效。

最终选择后,用户必须按下“更新”按钮,以便我可以根据他的输入过滤数据。结果将显示在 DataGridView 中。

但是,由于 Excel 表格上的数据变化很大,我曾经在第二个表单 (Waitform) 上有一个 ProgressBar,在 UI 上的 ProgressBar 显示时使 DataGridView 不可见在非 UI 任务(数据收集)期间可见。

我知道我应该使用任务或线程(或 BackGroundWorker)来保持 UI 响应。
话虽如此,它仍然冻结了我的整个应用程序。

//Update Button which uses all the userdefined filters
private async void updateButton_Click(object sender, EventArgs e)
{
    WaitBarDatagrid.Visible = true; //Progressbar is called WaitBarDatagrid
    WaitBarDatagrid.Style = ProgressBarStyle.Marquee;
    WaitBarDatagrid.MarqueeAnimationSpeed = 30;

    dataGridView1.Visible = false;
    await Task.Run(() => QueryToExcel());
    dataGridView1.DataSource = FileInfos;
    WaitBarDatagrid.Visible = false;
    dataGridView1.Visible = true;
}

private void QueryToExcel()
{
    this.Invoke((MethodInvoker)delegate ()
    {
        string fSize;
        if (FileSizeComboBox.Text == "All Data")
        { fSize = "0"; }
        else if (FileSizeComboBox.Text == "> 1 MB")
        { fSize = "1000"; } // 1MB = 1000kB 
        else if (FileSizeComboBox.Text == "> 10 MB")
        { fSize = "10000"; } // 10MB = 10.000kB
        else if (FileSizeComboBox.Text == "> 100 MB")
        { fSize = "100000"; } // 100MB = 100.000kB 
        else if (FileSizeComboBox.Text == "> 1 GB")
        { fSize = "1000000"; } // 1 GB = 1000.000 kB
        else
            fSize = "0";

        // The following ensures that all possibilities of User Definition are covered
        string user = "";
        string size = "";
        string sep = ""; //Seperator

        if (!string.IsNullOrEmpty(UserTextbox.Text))
        {
            user = $"[UserID] = '{UserTextbox.Text}'";
            sep = "AND";
        }

        if (!string.IsNullOrEmpty(FileSizeComboBox.Text))
        {
            size = $"{sep} [File Size] >= {fSize}";
            sep = "AND";
        }                    

        //Final Where CLAUSE based on User Input
        //string command = $@"{user} {size}{sep} [Date] <= {DateBox.Value.ToOADate()}";
        string command = $@"{user} {size} {sep} [Date] <= {DateBox.Value.ToOADate()}";

        //Call Data from Excel
        string connectionString = GetConnectionString(Datapath + RootCombobox.Text);
        string query = $@"SELECT * from [FileInfos$]  WHERE ({command})";
        DataTable dt = new DataTable();
            
        using (OleDbConnection conn = new OleDbConnection(connectionString))
        {
            conn.Open();
            using (OleDbDataAdapter dataAdapter = new OleDbDataAdapter(query, conn))
            {
                try
                {
                    dataAdapter.Fill(dt);
                    FileInfos = dt;
                }
                catch (System.Data.OleDb.OleDbException ex)
                {
                    MessageBox.Show(ex.ToString());
                }
            }                    
        }
    });
}

到目前为止,我还尝试将Userinputs 的值分配给全局变量,并将在它们对应的事件中更改。但是,即使调用我的 UI 也会冻结。它来自哪里?

【问题讨论】:

  • 即使调用我的 UI 冻结。它正在调用冻结它的 UI 线程。在另一个线程上工作就是不使用 UI 线程。如果你调用回来......从我在这里看到的,你不需要。您可以将所有必需的值(您的控件的属性)作为字符串传递给您的方法 (QueryToExcel()),并返回一个 DataTable。您可以在 private DataTable QueryToExcel(string[] paramarray) 中更改它,称为 var dt = await Task.Run(() =&gt; QueryToExcel(values[]));。您没有显示与 ProgressBar 相关的任何内容,但是,由于您要返回 DataTable...
  • 顺便说一句,由于您使用的是 DataAdapter,因此您不需要这个:conn.Open();。一个 MessageBox 不是一个好主意。它可能只是为了调试,但您可以在控制台(输出窗口)中写入错误。
  • 嗨@Jimi,我只是自己想出来的。我希望你在一个小时前回答:) 你还是个天才,请把它写成答案,这样我就可以选择它作为解决方案。

标签: c# multithreading winforms datagridview task


【解决方案1】:

QueryToExcel() 方法应该将工作排队以在 ThreadPool 线程上运行,让 UI 线程继续其自己的工作而不会冻结。
但是您注意到 UI 无论如何都会冻结,并说:

即使调用我的 UI 也会冻结

它正在从另一个冻结它的线程调用 UI 线程。
在另一个线程上工作就是不使用 UI 线程。如果我们从工作线程调用回 UI 线程,效果会丢失(或部分丢失,无论如何都令人讨厌)。

您还使用Invoke() 而不是BeginInvoke()。后者是异步执行的:如果调用的控件很忙或无法访问/无响应,它会立即返回并防止死锁。
无论如何,它不会阻止 UI 有时卡顿。

查看您在此处提供的代码,似乎根本不需要调用 UI 线程:辅助线程只需要一些控件的属性值,然后将 DataTable 分配给字段。

然后可以将所需的值作为参数传递给此方法,将控件的属性分配给某些变量或类的属性(因此更容易理解参数包含的内容)。

worker 方法可以更改

private DataTable QueryToExcel(string[] paramArray) 
Or
private DataTable QueryToExcel(SomeClass values) 

并且可以称为:

private async void updateButton_Click(object sender, EventArgs e)
{
    var dt = await Task.Run(() => QueryToExcel(values));
    Or
    dataGridView1.DataSource = await Task.Run(() => QueryToExcel(values));
}

QueryToExcel() 到 Excel:

  • 访问values 参数以设置查询或其他处理。
  • 创建数据库连接并填充数据表/数据集。
  • 处理所有创建的一次性对象(Connection/DataAdapter 等)
  • 返回一个数据表

【讨论】:

  • 现在我将控件属性分配给变量并将它们访问到“QueryToExcel()”。在这种情况下,它工作正常。但是,在 DB Connection 中使用“using”会自行处理 Connection/Adapter,不是吗?
  • 是的,确实如此。这是初始化OleDbConnection 的正确方法。我没有明确地为您发布了该说明(许多人忘记这样做)。您还可以使用 using 语句声明 DataTable。但这是另一个故事:)。顺便说一句,如果您对 Enigmativity 的回答投了反对票,请对此发表评论。
【解决方案2】:

您的代码基本上会跳转到非 UI 线程,然后又跳回 UI - 就好像您从未离开过 UI 线程一样。

您需要做的是在 UI 线程上完成所有 UI 工作,而仅在另一个线程上完成非 UI 工作。

试试这个代码:

// Define other methods and classes here
//Update Button which uses all the userdefined filters
private async void updateButton_Click(object sender, EventArgs e)
{
    WaitBarDatagrid.Visible = true; //Progressbar is called WaitBarDatagrid
                                    //    WaitBarDatagrid.Style = ProgressBarStyle.Marquee;
                                    //    WaitBarDatagrid.MarqueeAnimationSpeed = 30;

    dataGridView1.Visible = false;

    string fSize;
    if (FileSizeComboBox.Text == "All Data")
    { fSize = "0"; }
    else if (FileSizeComboBox.Text == "> 1 MB")
    { fSize = "1000"; } // 1MB = 1000kB 
    else if (FileSizeComboBox.Text == "> 10 MB")
    { fSize = "10000"; } // 10MB = 10.000kB
    else if (FileSizeComboBox.Text == "> 100 MB")
    { fSize = "100000"; } // 100MB = 100.000kB 
    else if (FileSizeComboBox.Text == "> 1 GB")
    { fSize = "1000000"; } // 1 GB = 1000.000 kB
    else
        fSize = "0";

    // The following ensures that all possibilities of User Definition are covered
    string user = "";
    string size = "";
    string sep = ""; //Seperator

    if (!string.IsNullOrEmpty(UserTextbox.Text))
    {
        user = $"[UserID] = '{UserTextbox.Text}'";
        sep = "AND";
    }

    if (!string.IsNullOrEmpty(FileSizeComboBox.Text))
    {
        size = $"{sep} [File Size] >= {fSize}";
        sep = "AND";
    }

    //Final Where CLAUSE based on User Input
    //string command = $@"{user} {size}{sep} [Date] <= {DateBox.Value.ToOADate()}";
    string command = $@"{user} {size} {sep} [Date] <= {DateBox.Value.ToOADate()}";

    await Task.Run(() => QueryToExcel(command, RootCombobox.Text));
    dataGridView1.DataSource = FileInfos;
    WaitBarDatagrid.Visible = false;
    dataGridView1.Visible = true;
}

private void QueryToExcel(string command, string RootCombobox_Text)
{
    //Call Data from Excel
    string connectionString = GetConnectionString(Datapath + RootCombobox_Text);
    string query = $@"SELECT * from [FileInfos$]  WHERE ({command})";
    DataTable dt = new DataTable();

    using (OleDbConnection conn = new OleDbConnection(connectionString))
    {
        conn.Open();
        using (OleDbDataAdapter dataAdapter = new OleDbDataAdapter(query, conn))
        {
            try
            {
                dataAdapter.Fill(dt);
                this.Invoke((MethodInvoker)delegate () { FileInfos = dt; });
            }
            catch (System.Data.OleDb.OleDbException ex)
            {
                this.Invoke((MethodInvoker)delegate () { MessageBox.Show(ex.ToString()); });
            }
        }
    }
}

它未经测试,但应该很接近。请注意,在任何非 UI 线程上都不会访问或更新任何 UI 元素。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-10-15
    • 1970-01-01
    • 1970-01-01
    • 2020-12-03
    • 2010-12-16
    • 2011-01-25
    • 2017-06-17
    • 2012-08-19
    相关资源
    最近更新 更多