通过进度条显示执行进度

10

我有一个愚蠢的问题,但却被卡住了。我正在从我的代码中执行存储过程,该过程需要一些时间,因此我正在显示一个进度条,用于显示执行进度,但存储过程执行时,没有任何东西可以使进度条的值递增。

这是我的代码:

void btnYes_Click(object sender, EventArgs e)
{
   if (DialogResult.Yes == MessageBox.Show("Are you sure", "", MessageBoxButtons.YesNo))
    {
        try
        {
         dbDataEntities db = new dbDataEntities();
        string myquery = "DECLARE @return_value int EXEC    @return_value = [dbo].[ssspUpdateMarksOfStudent] SELECT 'Return Value' = @return_value";
         //progressbar1.Maximum = 5000; 
         //progressbar1.value = ?; // how could i increment it
         //
         db.Database.ExecuteSqlCommand("myquery");



        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
    }
}

我也尝试使用秒表,通过设置当前值来实现,而不是在执行过程中递增。

Stopwatch st = new Stopwatch();
st.Start();
progressbar1.Maximum = 5000; 
progressbar1.Value = Convert.ToInt16(st.Elapsed.Seconds);
//My stored procedure call 
st.Stop(); 

那么这只能通过使用后台工作者来完成,还有其他方法吗?

我是编程新手,所以没有经常使用后台工作者,因此我正在尝试寻找替代方法。

请提供建议。提前致谢。


2
你可以考虑优化你的存储过程。 - geedubb
问题不在于我的存储过程,而是我无法通过进度条显示执行进度。 - Amit Bisht
你的存储过程中有迭代吗?是否有可能从SP中删除一个带有整数值的select语句? - Mad Dog Tannen
我不明白你在问什么,@KayNelson。 - Amit Bisht
简单的方法是使用BackgroundWorker。至于过程,优化它的解决方案是一个临时解决方案,因为该过程返回更多数据和更多数据。 - kostas ch.
显示剩余2条评论
5个回答

12

有一个有趣的想法,SqlConnection提供了InfoMessage事件,当服务器执行PRINT命令时会触发该事件。你可以在你的存储过程的某些部分打印信息并侦听此事件。

-- do something
PRINT '10 PERCENT COMPLETED';
-- do another thing
PRINT '20 PERCENT COMPLETED';
...
PRINT '100 PERCENT COMPLETED';

除此之外,使用@hamlet-hakobyan的解决方案。只需显示一个无限进度条。


更新:更新以包括完整的解决方案

首先,我不喜欢给出完整的答案。这会阻止人们思考解决方案的能力。相反,我喜欢引导人们走向正确的道路,这样他们就可以自己完成。但是这里还是提供了完整的解决方案。在W7x64SP1和SQL2012下使用VS2012、NET4、MSIL测试通过。

Create Procedure usp_LongProcess As Begin

    Declare @i Int;
    Declare @msg VarChar(50);
    Set @i = 0;
    while (@i < 100) Begin
        WaitFor Delay '00:00:02';

        Set @i = @i + 10;
        Set @msg = Convert(VarChar(10), @i) + ' PERCENT COMPLETE';
        RaisError(@msg, 1, 1) With NoWait
    End
End

以下是我的表单,包括:

  • 一个按钮 (CallSpButton)
  • 一个进度条 (progress)
  • 一个标签 (statusLabel) 和
  • 一个带有 WorkerReportsProgress 设置为 true 的后台工作程序 (SpCaller)。

截图

最后是发起调用的代码。

private void CallSpButton_Click(object sender, EventArgs e)
{
    CallSpButton.Enabled = false;
    SpCaller.RunWorkerAsync();
}

private void SpCaller_DoWork(object sender, DoWorkEventArgs e)
{
    var self = (BackgroundWorker) sender;

    var cb = new SqlConnectionStringBuilder
    {
        DataSource = ".", 
        InitialCatalog = "Sandbox", 
        IntegratedSecurity = true
    };

    using (var cn = new SqlConnection(cb.ToString()))
    {
        cn.FireInfoMessageEventOnUserErrors = true;
        cn.Open();
        cn.InfoMessage += (o, args) => self.ReportProgress(0, args.Message);

        using (var cmd = cn.CreateCommand())
        {
            cmd.CommandText = "usp_LongProcess";
            cmd.CommandType = CommandType.StoredProcedure;

            cmd.ExecuteNonQuery();
        }
    }
}

private void SpCaller_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    CallSpButton.Enabled = true;
}

private void SpCaller_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    var message = Convert.ToString(e.UserState);
    Debug.WriteLine(message);
    statusLabel.Text = message;

    if (message.EndsWith(" PERCENT COMPLETE"))
    {
        int percent;
        if (int.TryParse(message.Split(' ')[0], out percent))
            progress.Value = percent;
    }
}

当代码执行存储过程时,我该如何显示其状态?因为UI在存储过程执行时会卡住。 - Amit Bisht
UI卡住了,因为你错误地使用了BackgroundWorker。在调用RunWorkerAsync之后,你仍然运行代码直到worker完成。你应该创建另一个计时器来更新进度条,并使用RunWorkerCompleted来跟踪完成情况。 - Erdogan Kurtur
正如我在问题中提到的,我没有使用后台工作者,因此如果不使用它,用户界面会被卡住。 - Amit Bisht

7

看起来你正在主线程上运行存储过程。

这意味着在调用运行期间,你的应用程序将无响应,因为消息队列被阻塞。这可能不是一个好主意。我建议将所有长时间运行的调用移动到另一个线程。

Background Worker是一个设计用于此目的的类,它允许你报告长时间运行任务的进度,以便你可以更新UI,但由于你的任务只有一个项目,所以没有太多可报告的,所以它可能不是你的最佳选择。

也许你想要类似如下形式(猜测语法,因为我手头没有编译器),并参考Windows Forms ProgressBar: Easiest way to start/stop marquee?

progressbar1.Style = ProgressBarStyle.Marquee; //thanks Hamlet
progressbar1.MarqueeAnimationSpeed = 30;

//run the long running thing on a background thread.
var bgTask = Task.Factory.StartNew(() => db.Database.ExecuteSqlCommand("myquery"));

//when it's done, back on this thread, let me know and we'll turn the progress bar off
bgTask.ContinueWith(resultTask => {
    progressBar1.Style = ProgressBarStyle.Continuous;
    progressBar1.MarqueeAnimationSpeed = 0;
 }, TaskScheduler.FromCurrentSynchronizationContext());

жңүжІЎжңүеҠһжі•дҪҝз”ЁBackgroundWorkerзҡ„ReportProgressе’ҢProgressChangedж–№жі•жқҘеўһеҠ еёёи§„зҡ„ProgressBarпјҹ - SuperPrograman
如果您的长时间运行的任务有适合报告进度的地方,您可以在工作线程上调用ReportProgress方法,然后ProgressChanged事件将在UI线程上触发。我认为您可能需要明确启用它。阅读Background Worker文档可以向您展示如何做到这一点:http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.reportprogress(v=vs.110).aspx - jaquesri
1
当您可以使用Tasks时,没有理由使用BackgroundWorker。它比Task.Run/IProgress<>的组合没有任何额外的优点,同时需要更多的代码、更重,并且无法提供超过简单数字之外的任何进度。 - Panagiotis Kanavos
谢谢@PanagiotisKanavos,我没有注意到新的IProgress<>接口,它看起来比BackgroundWorker更清晰。 - jaquesri

2

2

请按照以下方法操作。

  1. 首先计算执行存储过程的时间。 db.Database.ExecuteSqlCommand("myquery"); 输入代码

  2. 使用C#中的后台工作器控件,为此查询分配一个单独的线程/(进程的一部分)进行执行。(在do_work事件中)

  3. 同样地,使用后台工作器为显示进度栏状态分配一个单独的进程线程。(在Progress-Changed事件中)只需将值增加10%并休眠一段时间(根据消耗查询所需的时间将其分为十个部分)。完成后,将Progressbar.value设置为100。(在Run-Worker-CompletedEvent事件中)

请同时调用两个线程 backgroundWorker1.RunWorkerAsync();

我认为这解决了您的问题。



0

我学习了后台工作程序,并且按照以下方式实现: 我将我的过程执行放在另一个线程中,而将进度条放在GUI线程中。

 BackgroundWorker bg = new BackgroundWorker();
 Boolean stopwork = true;
 Private void btnYes_Click(object sender, EventArgs e)
        {
            if (DialogResult.Yes == MessageBox.Show(clsGlobalObjectRefrances.OMessageString.SureWant2UpdateMarks, "", MessageBoxButtons.YesNo))
            {
                try
                {

                    bg.DoWork += bg_DoWork;
                    bg.RunWorkerCompleted += bg_RunWorkerCompleted;
                    bg.RunWorkerAsync();
                    pgbUpdateMarks.Maximum = 60;
                    Stopwatch st = new Stopwatch();
                    st.Start();
                    while(stopwork)
                    {
                        pgbUpdateMarks.Value = st.Elapsed.Seconds;
                    }
                    pgbUpdateMarks.Value = 0;
                    MessageBox.Show("Executed sucessfully");
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message);
                }
            }
        }

DoWork在哪里

 void bg_DoWork(object sender, DoWorkEventArgs e)
        {

        dbDataEntities db = new dbDataEntities();
        string myquery = "DECLARE @return_value int EXEC    @return_value = [dbo].[ssspUpdateMarksOfStudent] SELECT 'Return Value' = @return_value";
        db.Database.ExecuteSqlCommand("myquery");
        stopwork = false;
        }

在.NET 3.5之前,这可能是一个可以接受但不太好的解决方案。.NET 4允许您以更简单的方式执行相同的操作。您只需使用6行代码即可完成相同的操作。顺便说一下,您调用该过程的方式不仅是错误的,而且很危险。只需创建一个类型为StoredProcedure的DbCommand并传递参数即可。 - Panagiotis Kanavos

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接