【问题标题】:Animate Progress Bar while SQL ExecutesSQL 执行时动画进度条
【发布时间】:2017-01-30 01:17:24
【问题描述】:

在我的 C# WindowsForm 应用程序中,我尝试使用固定持续时间(在我的情况下为 6500 毫秒)从头到尾为进度条设置动画。这大致是 SQL 执行所需的时间。

我尝试在执行 SQL 之前运行 animate 方法,但是在执行 SQL 时,似乎认为整个线程都被冻结了。我不想在出现异常的情况下异步运行 SQL。

我是多线程和后台工作进程的新手,虽然我相信这可能是关键?

如果有人能引导我朝着正确的方向前进,将不胜感激!

private void btnInternalMovement_Click(object sender, EventArgs e)
{

    AnimateProgBar(6500);


    // ***** BLAH MORE CODE HERE ******

    using (SqlCommand cmd = new SqlCommand("CatamacInternalStockMovement_C", SqlConnection))
    {

        cmd.CommandType = CommandType.StoredProcedure;
        cmd.Parameters.AddWithValue("@ItemName", items);
        cmd.Parameters.AddWithValue("@Quantity", quantities);
        cmd.Parameters.AddWithValue("@WarehouseFrom", cbxFromWarehouse.SelectedValue.ToString());
        cmd.Parameters.AddWithValue("@WarehouseTo", cbxToWarehouse.SelectedValue.ToString());
        cmd.Parameters.AddWithValue("@LocationFrom", cbxFromZone.SelectedValue.ToString());
        cmd.Parameters.AddWithValue("@LocationTo", cbxToZone.SelectedValue.ToString());
        cmd.Parameters.AddWithValue("@UOM", "Each");
        cmd.Parameters.AddWithValue("@TransferDate", DateTime.Now);
        cmd.Parameters.AddWithValue("@User", User);
        cmd.ExecuteNonQuery();

    }

    // PROGRESS BAR ONLY STARTS ANIMATING HERE
    MessageBox.Show("Success!");

}

这里是计时器/动画方法的代码。

private void timer1_Tick(object sender, EventArgs e)
{
    if (progressBar1.Value < 100)
    {
        progressBar1.Value += 1;
        progressBar1.Refresh();
    }
    else
    {
        timer1.Enabled = false;
    }
}

public void AnimateProgBar(int milliSeconds)
{
    if (timer1.Enabled) return;

    progressBar1.Value = 0;
    timer1.Interval = milliSeconds / 100;
    timer1.Enabled = true;
}

【问题讨论】:

  • 您需要使用后台线程或await
  • 在我回答之前,先检查一下您是否使用 .NET 4,对吧?
  • 更新了标签,这个特定的应用程序正在运行 .NET 4.5

标签: c# multithreading winforms .net-4.5 backgroundworker


【解决方案1】:

因为cmd.ExecuteNonQuery();btnInternalMovement_Click 的回调内部执行,所以它在UI 线程上执行。在完成任何回调(包括btnInternalMovement_Click)所需的时间内,UI 将被阻止。如果cmd.ExecuteNonQuery() 需要很长时间才能完成,那么您会在 UI 中看到更新被冻结的情况;无响应的键盘和鼠标输入。

async/await 与网络绑定调用

您对数据库的调用是 I/O-bound 而不是 CPU-bound 所以您不应该使用Task.Run() Jim's incorrect answer。如果您想使用async/await,请确保您使用 IOCP 表单,并且在整个通话期间不要启动和/或浪费 不必要的工作线程

当有一个非常好的ExecuteNonQueryAsync() 时,避免使用Task.Run()ExecuteNonQueryAsync() 在构建时考虑了 IOCP,并且比执行以下操作更有效:

await Task.Run(() =>
   {
      // I/O-bound: bad!  DON'T DO THIS for I/O-bound operations such 
      //as disk; network; DB etc
   });

改变这个:

private void btnInternalMovement_Click(object sender, EventArgs e)
{

    AnimateProgBar(6500);


    // ***** BLAH MORE CODE HERE ******

    using (SqlCommand cmd = new SqlCommand("CatamacInternalStockMovement_C", SqlConnection))
    {

        cmd.CommandType = CommandType.StoredProcedure;
        cmd.Parameters.AddWithValue("@ItemName", items);
        cmd.Parameters.AddWithValue("@Quantity", quantities);
        cmd.Parameters.AddWithValue("@WarehouseFrom", cbxFromWarehouse.SelectedValue.ToString());
        cmd.Parameters.AddWithValue("@WarehouseTo", cbxToWarehouse.SelectedValue.ToString());
        cmd.Parameters.AddWithValue("@LocationFrom", cbxFromZone.SelectedValue.ToString());
        cmd.Parameters.AddWithValue("@LocationTo", cbxToZone.SelectedValue.ToString());
        cmd.Parameters.AddWithValue("@UOM", "Each");
        cmd.Parameters.AddWithValue("@TransferDate", DateTime.Now);
        cmd.Parameters.AddWithValue("@User", User);
        cmd.ExecuteNonQuery();

    }

    // PROGRESS BAR ONLY STARTS ANIMATING HERE
    MessageBox.Show("Success!");

}

...到:

private async void btnInternalMovement_Click(object sender, EventArgs e)
{

    AnimateProgBar(6500);


    // ***** BLAH MORE CODE HERE ******

    using (SqlCommand cmd = new SqlCommand("CatamacInternalStockMovement_C", SqlConnection))
    {

        cmd.CommandType = CommandType.StoredProcedure;
        cmd.Parameters.AddWithValue("@ItemName", items);
        cmd.Parameters.AddWithValue("@Quantity", quantities);
        cmd.Parameters.AddWithValue("@WarehouseFrom", cbxFromWarehouse.SelectedValue.ToString());
        cmd.Parameters.AddWithValue("@WarehouseTo", cbxToWarehouse.SelectedValue.ToString());
        cmd.Parameters.AddWithValue("@LocationFrom", cbxFromZone.SelectedValue.ToString());
        cmd.Parameters.AddWithValue("@LocationTo", cbxToZone.SelectedValue.ToString());
        cmd.Parameters.AddWithValue("@UOM", "Each");
        cmd.Parameters.AddWithValue("@TransferDate", DateTime.Now);
        cmd.Parameters.AddWithValue("@User", User);
        await cmd.ExecuteNonQueryAsync(); // <----- NEW

    }

    // PROGRESS BAR ONLY STARTS ANIMATING HERE
    MessageBox.Show("Success!");

}

另见

【讨论】:

  • 谢谢 MickyD,有很多信息需要细细琢磨 :) 我会编辑我的代码!
  • @BrendanGooden 别担心好伙伴。祝你一切顺利:) (查看Stephen Cleary's blog,因为他更详细地介绍了它)
【解决方案2】:

您的代码中可能存在一些错误;

  1. 您似乎没有启动()您的计时器,这可能应该进入 AnimateProgBar。

  2. AnimateProgBar 在它的参数中期望毫秒,但你这样做

    timer1.Interval = 毫秒/100;

这是错误的,因为 .Interval 也需要毫秒。

要回答您的问题,您只需将 btnInternalMovement_Click 更改为

    private async void btnInternalMovement_Click(object sender, EventArgs e)
{

    AnimateProgBar(6500);


    // ***** BLAH MORE CODE HERE ******
await Task.Run(() => { 
    using (SqlCommand cmd = new SqlCommand("CatamacInternalStockMovement_C", SqlConnection))
    {

        cmd.CommandType = CommandType.StoredProcedure;
        cmd.Parameters.AddWithValue("@ItemName", items);
        cmd.Parameters.AddWithValue("@Quantity", quantities);
        cmd.Parameters.AddWithValue("@WarehouseFrom", cbxFromWarehouse.SelectedValue.ToString());
        cmd.Parameters.AddWithValue("@WarehouseTo", cbxToWarehouse.SelectedValue.ToString());
        cmd.Parameters.AddWithValue("@LocationFrom", cbxFromZone.SelectedValue.ToString());
        cmd.Parameters.AddWithValue("@LocationTo", cbxToZone.SelectedValue.ToString());
        cmd.Parameters.AddWithValue("@UOM", "Each");
        cmd.Parameters.AddWithValue("@TransferDate", DateTime.Now);
        cmd.Parameters.AddWithValue("@User", User);
        cmd.ExecuteNonQuery();

    }

    // PROGRESS BAR ONLY STARTS ANIMATING HERE
    MessageBox.Show("Success!");
});
}

请注意,该方法现在被标记为异步,并且长时间运行的内容位于 Task.Run 中(将其置于后台)。您真的不希望 UI 线程中发生长时间运行的任务。

显然,异步编程比我在这个答案中提出的要多得多,所以请参阅https://msdn.microsoft.com/en-us/library/mt674882.aspx

【讨论】:

  • await Task.Run() 在调用网络绑定时不正确使用 TPL。您的代码假设它受 CPU 限制,但事实并非如此。见my answer
  • @MickyD 你说得对,我的答案不太理想,我没有考虑他的任务细节就匆忙给出了答案。
猜你喜欢
  • 2014-04-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-19
  • 1970-01-01
  • 1970-01-01
  • 2023-03-27
相关资源
最近更新 更多