使用在另一个线程上运行的任务冻结Waitform上的进度栏

时间:2019-05-07 07:16:21

标签: c# multithreading winforms task oledb

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

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

但是,由于Excel工作表上的数据相差很大,因此我曾经在第二个窗体(Waitform)上有一个进度栏,或者在用户界面期间在UI上可见进度栏时使datagridview不可见。非UI任务(数据收集)。
我知道我应该使用Task或Thread(或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也会冻结。它来自哪里?

2 个答案:

答案 0 :(得分:2)

应该使用QueryToExcel()方法将要在线程池上运行的工作排队,以使UI线程继续其自身的工作而不会冻结。
但是您会注意到UI仍然冻结,并说:

  

即使调用我的UI也会冻结

它正在从冻结它的另一个线程中调用UI线程。
在另一个线程上进行工作是关于不使用UI线程。如果我们从工作线程中调用UI线程,则 effect 会丢失(或部分丢失,无论如何都会令人烦恼)。

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

看看这里显示的代码,似乎根本不需要调用UI线程:辅助线程只需要某些控件的属性值,然后将DataTable分配给字段。

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

可以在

中更改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等)
  • 返回数据表

答案 1 :(得分:1)

您的代码基本上会跳到非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元素。