SQL 执行时动画进度条

Animate Progress Bar while SQL Executes

在我的 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;
}

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

  1. 你似乎没有 Start() 你的计时器,它可能应该放在 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

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

async/await 与 Network-bound 通话

你对数据库的调用是 I/O-bound 而不是 CPU-bound 所以你 应该不是 按照 使用 Task.Run()。如果你想使用 async/await 确保你使用 IOCP 形式而不是旋转 and/or 浪费一个 unnessary worker thread through-out称呼。

避免使用 Task.Run(),因为 ExecuteNonQueryAsync() 非常好。 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!");

}

另见

  • When correctly use Task.Run and when just async-await