views:

8621

answers:

5

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.

+1  A: 

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
@Akash- go-goo-go isn't using a background thread explicity- they are using a BackgroundWorker component.
RichardOD
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.
Unfortuanately, winforms Invoke doesn't allow for anonymous delegates at all. You must use `new` with a `delegate` type :(
Matthew Scharley
The whole point of using BackgroundWorker.ReportProgress is to report the change to the UI thread, so it can update the controls.
Noam Gal
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
@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
@Akash Kava, yep, i'll try this one
Winforms does allow anonymous delegates. this.Invoke((MethodInvoker)delegate() { //UI code here });
sindre j
+2  A: 

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
+3  A: 

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) {
    progressBar.Value += 5;
    if (progressBar.Value > 120)
        progressBar.Value = 0;
}

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
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.
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 likehttp://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.
A: 

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.

A: 

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