20

如何在 C# 中为数据库调用实现进度条和后台工作程序?

我确实有一些处理大量数据的方法。它们是运行时间相对较长的操作,所以我想实现一个进度条,让用户知道某些事情正在实际发生。

我想过使用进度条或状态条标签,但由于只有一个 UI 线程,执行数据库处理方法的线程,UI 控件没有更新,使得进度条或状态条标签对我来说毫无用处。

我已经看过一些例子,但它们处理 for 循环,例如:

for(int i = 0; i < count; i++)
{ 
    System.Threading.Thread.Sleep(70);
    // ... do analysis ...
    bgWorker.ReportProgress((100 * i) / count);
}

private void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    progressBar.Value = Math.Min(e.ProgressPercentage, 100);
}

我正在寻找更好的例子。

4

6 回答 6

18

有些人可能不喜欢它,但这就是我所做的:

private void StartBackgroundWork() {
    if (Application.RenderWithVisualStyles)
        progressBar.Style = ProgressBarStyle.Marquee;
    else {
        progressBar.Style = ProgressBarStyle.Continuous;
        progressBar.Maximum = 100;
        progressBar.Value = 0;
        timer.Enabled = true;
    }
    backgroundWorker.RunWorkerAsync();
}

private void timer_Tick(object sender, EventArgs e) {
    if (progressBar.Value < progressBar.Maximum)
        progressBar.Increment(5);
    else
        progressBar.Value = progressBar.Minimum;
}

Marquee 样式需要启用 VisualStyles,但它会自行持续滚动而无需更新。我将其用于不报告进度的数据库操作。

于 2009-08-11T12:56:39.560 回答
11

如果你不知道进度,你不应该通过滥用进度条来伪造它,而只是显示某种繁忙的图标,如 en.wikipedia.org/wiki/Throbber#Spinning_wheel 在开始任务时显示它并在它完成时隐藏它完成的。这将使 GUI 更加“诚实”。

于 2010-04-07T14:49:40.277 回答
6

当您在后台线程上执行操作并想要更新 UI 时,您不能从后台线程调用或设置任何内容。对于 WPF,您需要 Dispatcher.BeginInvoke,对于 WinForms,您需要 Invoke 方法。

WPF:

// assuming "this" is the window containing your progress bar..
// following code runs in background worker thread...
for(int i=0;i<count;i++)
{
    DoSomething();
    this.Dispatcher.BeginInvoke((Action)delegate(){
         this.progressBar.Value = (int)((100*i)/count);
    });
}

赢表格:

// assuming "this" is the window containing your progress bar..
// following code runs in background worker thread...
for(int i=0;i<count;i++)
{
    DoSomething();
    this.Invoke(delegate(){
         this.progressBar.Value = (int)((100*i)/count);
    });
}

对于 WinForms 委托可能需要一些转换,或者您可能需要很少的帮助,现在不记得确切的语法了。

于 2009-08-11T12:15:09.967 回答
5

与后台工作人员报告进度的想法是通过发送“完成百分比”事件。您自己负责确定以某种方式完成了“多少”工作。不幸的是,这通常是最困难的部分。

在您的情况下,大部分工作与数据库相关。据我所知,无法直接从数据库中获取进度信息。但是,您可以尝试做的是动态拆分工作。例如,如果您需要读取大量数据,则可能是一种简单的实现方式。

  • 确定要检索的行数(SELECT COUNT(*) FROM ...)
  • 将实际阅读分成更小的块,每完成一个块就报告进度:

    for (int i = 0; i < count; i++)
    {
        bgWorker.ReportProgress((100 * i) / count);
        // ... (read data for step i)
    }
    
于 2009-08-11T12:27:22.420 回答
2

我没有编译这个,因为它是为了概念证明。这就是我过去为数据库访问实现进度条的方式。此示例显示使用 System.Data.SQLite 模块访问 SQLite 数据库

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{   
    // Get the BackgroundWorker that raised this event.
    BackgroundWorker worker = sender as BackgroundWorker;
    using(SQLiteConnection cnn = new SQLiteConnection("Data Source=MyDatabase.db"))
    {
        cnn.Open();
        int TotalQuerySize = GetQueryCount("Query", cnn); // This needs to be implemented and is not shown in example
        using (SQLiteCommand cmd = cnn.CreateCommand())
        {
            cmd.CommandText = "Query is here";
            using(SQLiteDataReader reader = cmd.ExecuteReader())
            {
                int i = 0;
                while(reader.Read())
                {
                    // Access the database data using the reader[].  Each .Read() provides the next Row
                    if(worker.WorkerReportsProgress) worker.ReportProgress(++i * 100/ TotalQuerySize);
                }
            }
        }
    }
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    this.progressBar1.Value = e.ProgressPercentage;
}

private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    // Notify someone that the database access is finished.  Do stuff to clean up if needed
    // This could be a good time to hide, clear or do somthign to the progress bar
}

public void AcessMySQLiteDatabase()
{
    BackgroundWorker backgroundWorker1 = new BackgroundWorker();
    backgroundWorker1.DoWork += 
        new DoWorkEventHandler(backgroundWorker1_DoWork);
    backgroundWorker1.RunWorkerCompleted += 
        new RunWorkerCompletedEventHandler(
    backgroundWorker1_RunWorkerCompleted);
    backgroundWorker1.ProgressChanged += 
        new ProgressChangedEventHandler(
    backgroundWorker1_ProgressChanged);
}
于 2010-04-07T15:38:57.700 回答
0

这将很有帮助。易于实施,100% 测试。

for(int i=1;i<linecount;i++)
{
 progressBar1.Value = i * progressBar1.Maximum / linecount;  //show process bar counts
 LabelTotal.Text = i.ToString() + " of " + linecount; //show number of count in lable
 int presentage = (i * 100) / linecount;
 LabelPresentage.Text = presentage.ToString() + " %"; //show precentage in lable
 Application.DoEvents(); keep form active in every loop
}
于 2017-03-24T08:56:19.683 回答