20

How do I implement a progress bar and backgroundworker for database calls in C#?

I do have some methods that deal with large amounts of data. They are relatively long running operations, so I want to implement a progress bar to let the user know that something is actually happening.

I thought of using progress bar or status strip label, but since there is a single UI thread, the thread where the database-dealing methods are executed, UI controls are not updated, making the progress bar or status strip label are useless to me.

I've already seen some examples, but they deal with for-loops, ex:

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);
}

I'm looking for better examples.

George Stocker
  • 57,289
  • 29
  • 176
  • 237
  • 20
    Here's a pretty good general rule: if you find yourself typing Thread.Sleep(n), hit the backspace key until it isn't there any more. There are legitimate uses for Thread.Sleep, but usually it causes more problems than it solves. – MusiGenesis Aug 11 '09 at 12:12
  • 2
    what database are you using? Depending on the sort of access to the Database, you may be able to determine the progress. For example if your using a datareader, you can execute a 'count' query to see how much will be returned, then use that to determine the % complete as you read the response through the datareader. If your using a DataAdapter to fill a table, you could use an event on the table that notifies when a row is added and use that to update the % complete of the fill. – galford13x Apr 07 '10 at 15:09

7 Answers7

19

Some people may not like it, but this is what I do:

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;
}

The Marquee style requires VisualStyles to be enabled, but it continuously scrolls on its own without needing to be updated. I use that for database operations that don't report their progress.

Kyle Gagnet
  • 2,294
  • 2
  • 20
  • 27
  • that seemed to be faster and cleaner in code for the moment, since this issue resulted so time-consuming, but i'll keep looking for a better solution based on what @Akash Kava discussed. so i'm marking this as an temporary good answer. –  Aug 11 '09 at 14:09
  • 18
    Graphical User interface components are meant to carry a meaning and a progress bar is meant to indicate "how much of the task is done" aka "progress". While you're certainly not the only one doing it, if you can't know the progress you should not fake it by abusing a progress bar, instead just display some sort of busy icon like http://en.wikipedia.org/wiki/Throbber#Spinning_wheel Show it when starting the task and hide it when it's finished. That would make for a more "honest" GUI. – user282727 Apr 07 '10 at 14:48
  • Your `if` statement in `timer_Tick` will never be true, since this code will throw an ArgumentException if `progressBar.Value + 5` is larger than `progressBar.Maximum` – alldayremix Jun 28 '13 at 22:30
  • You're right, @alldayremix. I've updated the snippet to use ProgressBar.Increment() instead of manually incrementing ProgressBar.Value. – Kyle Gagnet Aug 13 '13 at 20:26
  • Note: If you want to just use Marquee style, you don't need the timer. It animates automatically (Marquee means the moving thing starts again when it reaches the end). Apparently, you cannot set the Marquee width. I like that suggestion above to use a spinning wheel instead. Just add a gif resource under project settings, resources (free, cool gif spinners are here http://www.chimply.com/Generator) and put it in a picture-box control and it animates automatically. Note, of course, that you cannot freeze the main UI thread or animations will also freeze. – Curtis Yallop Mar 14 '14 at 20:38
  • Also helpful for dialogs: You can disable the Form "ControlBox" property disables the close/maximize/minimize buttons. .ShowDialog() puts the window on-top. Form property "StartPosition" centers the window. – Curtis Yallop Mar 14 '14 at 20:55
  • Just to address the criticism from user282727, the entire purpose of the marquee style progress bar is to indicate that a running task's progress is unknown. The assertion that progress bars can only indicate "how much of the task is done" goes against Microsoft's documentation: https://msdn.microsoft.com/en-us/Library/bb760816%28v=vs.85%29.aspx#Marquee_Style Also, my solution only uses the "fake" progress when the application can't use VisualStyles. In that situation, I'd rather show a fake progress bar that keeps resetting than show nothing at all. – Kyle Gagnet Jan 23 '15 at 01:05
11

If you can't know the progress you should not fake it by abusing a progress bar, instead just display some sort of busy icon like en.wikipedia.org/wiki/Throbber#Spinning_wheel Show it when starting the task and hide it when it's finished. That would make for a more "honest" GUI.

user282727
  • 674
  • 1
  • 8
  • 21
6

When you perform operations on Background thread and you want to update UI, you can not call or set anything from background thread. In case of WPF you need Dispatcher.BeginInvoke and in case of WinForms you need Invoke method.

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);
    });
}

WinForms:

// 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);
    });
}

for WinForms delegate may require some casting or you may need little help there, dont remember the exact syntax now.

Akash Kava
  • 39,066
  • 20
  • 121
  • 167
  • @Akash- go-goo-go isn't using a background thread explicity- they are using a BackgroundWorker component. – RichardOD Aug 11 '09 at 12:19
  • thnx, but the for-loop used in the example is useless to me, because the DoSomething() method is a call updating, let's say 150.000 records in the database, and no one want to do this for 'count' times. –  Aug 11 '09 at 12:20
  • Unfortuanately, winforms Invoke doesn't allow for anonymous delegates at all. You must use `new` with a `delegate` type :( – Matthew Scharley Aug 11 '09 at 12:23
  • The whole point of using BackgroundWorker.ReportProgress is to report the change to the UI thread, so it can update the controls. – Noam Gal Aug 11 '09 at 12:32
  • I know, I have used BackgroundWorker component, if you look into Reflector, BackgroundWorker doesnt report progress on UI Thread, and that causes problems because we get errors like you cant access ui properties in non ui threads, so thats why we dont use it. Even when you use BackgroundWorker, you still need to do BeginInvoke or Invoke to update properties. Because the event does not get fired on UI thread !! we ran into some problem so we stopped using it that way, if .Net has some update on it, I am aware of it. – Akash Kava Aug 11 '09 at 12:42
  • 1
    @go-goo-go , I understand , however what can be done is you can execute your query on Pages of data, because if I am correct, if you are looking for your database to give you some progress of execution, sorry, no database in my knowledge provides this. And you can certainly use database update calls in background worker thread, why you think you have to execute that only on UI thread? – Akash Kava Aug 11 '09 at 12:44
  • @Akash Kava, yep, i'll try this one –  Aug 11 '09 at 12:51
  • Winforms does allow anonymous delegates. this.Invoke((MethodInvoker)delegate() { //UI code here }); – sindre j Aug 11 '09 at 13:02
5

The idea behind reporting progress with the background worker is through sending a 'percent completed' event. You are yourself responsible for determining somehow 'how much' work has been completed. Unfortunately this is often the most difficult part.

In your case, the bulk of the work is database-related. There is to my knowledge no way to get progress information from the DB directly. What you can try to do however, is split up the work dynamically. E.g., if you need to read a lot of data, a naive way to implement this could be.

  • Determine how many rows are to be retrieved (SELECT COUNT(*) FROM ...)
  • Divide the actual reading in smaller chunks, reporting progress every time one chunk is completed:

    for (int i = 0; i < count; i++)
    {
        bgWorker.ReportProgress((100 * i) / count);
        // ... (read data for step i)
    }
    
jeroenh
  • 26,362
  • 10
  • 73
  • 104
2

I have not compiled this as it is meant for a proof of concept. This is how I have implemented a Progress bar for database access in the past. This example shows access to a SQLite database using the System.Data.SQLite module

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);
}
galford13x
  • 2,483
  • 4
  • 30
  • 39
0

This will Helpfull.Easy to implement,100% tested.

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
}
0

You have to execute the process from a thread, and from the thread you invoke the progress bar and change its value, maybe this example helps you

public void main()
    {
        int count = 20;
        progressbar.Maximum = count;
        progressbar.Value = 0;

        new Thread(() => Work(progressbar, count)).Start();
    }
    public static void Work(ProgressBar progressbar, int count)
    {
        for (int i = 0; i <= count; i++)
        {
            Thread.Sleep(70);
            // ... do analysis ...
            Application.Current.Dispatcher.Invoke(new Action(() =>
            {
                progressbar.Value = i;
            }));
            
            
    }
    }