4

I have a fairly complex program so I won't dump the whole thing in here. Here's a simplified version:

class Report {
    private BackgroundWorker worker;

    public Report(BackgroundWorker bgWorker, /* other variables, etc */) {
        // other initializations, etc
        worker = bgWorker;
    }

    private void SomeCalculations() {
        // In this function, I'm doing things which may cause fatal errors.
        // Example: I'm connecting to a database.  If the connection fails, 
        // I need to quit and have my background worker report the error
    }
}


// In the GUI WinForm app:
// using statements, etc.
using Report;

namespace ReportingService {
    public partial class ReportingService : Form {

        // My background worker
        BackgroundWorker theWorker = new BackgroundWorker() {
            WorkerReportsProgress = true
        };

        // The progress changed event
        void worker_ProgressChanged(object sender, ProgressChangedEventArgs e) {
            // e.UserState and e.ProgressPercentage on some labels, etc.
        }

        // The do work event for the worker, runs the number crunching algorithms in SomeCalculations();
        void worker_DoWork(object sender, DoWorkEventArgs e) {
            Report aReport = e.Argument as Report;

            aReport.SomeCalculations();
        }

        // The completed event, where all my trouble is.  I don't know how to retrieve the error,
        // or where it originates from.
        void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
            // How, exactly, do I get this error message? Who provides it? How?
            if (e.Error != null) {
                MessageBox.Show("Error: " + (e.Error as Exception).ToString());
            }
            else if (e.Cancelled) {
                MessageBox.Show("Canceled");
            }
            // operation succeeded
            else {
                MessageBox.Show("Success");
            }
        }

        // Initialization of the forml, etc
        public ReportingService() {
            InitializeComponent();

            theWorker.ProgressChanged += worker_ProgressChanged;
            theWorker.DoWork += worker_DoWork;
            theWorker.RunWorkerCompleted += worker_RunWorkerCompleted;
        }

        // A button that the user clicks to execute the number crunching algorithm
        private void sumButton_Click(object sender, EventArgs e) {
            Report myReport = new Report(theWorker, /* some other variables, etc */)
            theWorker.RunWorkerAsync(myReport);
        }
    }
}

Here's my logic, and please correct me if I'm going about this the wrong way:

  1. I abstracted the class out of the GUI because it's ~2000 lines and needs to be it's own self contained object.

  2. I pass the background worker into my class so that I can report back the progress of my number crunching.

What I don't know how to do is let the background worker know that an error has happened inside my class. In order to get the RunWorkerCompleted argument as an exception, where does my try/catch block need to go, and what should I do in the catch block?

Thanks for your help!

EDIT:

I've tried the following things to test the error handling:

Keep in mind I corrupted my database connection string to purposefully receive an error message.

In my class I do:

// My number crunching algorithm contained within my class calls a function which does this:

// try {
    using (SqlConnection c = GetConnection()) {  // note: I've corrupted the connection string on purpose
        c.Open();  // I get the exception thrown here
        using (SqlCommand queryCommand = new SqlCommand(query, c)) { /* Loop over query, etc. */ }
        c.Close();  
    }
// } catch (Exception e) { }

1. From my understanding, an unhandled exception gets cast to the Error portion of the RunWorkerCompletedEventArgs? When I try this I get the following:

// In my winform application I initialize my background worker with these events:

void gapBW_DoWork(object sender, DoWorkEventArgs e) {
    Report aReport = e.Argument as Report;
    Report.Initialize();    // takes ~1 minute, throws SQL exception
    Report.GenerateData();  // takes around ~2 minutes, throws file IO exceptions
}

void gapBW_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {   
    if (e.Error != null) {  // I can't get this to trigger, How does this error get set?
        MessageBox.Show("Error: " + (e.Error as Exception).ToString());
    }
    else if (e.Cancelled) {
        MessageBox.Show("Canceled: " + (e.Result).ToString());
    }
    else {
        MessageBox.Show("Success");
    }
}

Visual studio says that my application chokes on c.Open() failing with an unhandled exception.

2. When I put a try/catch block in my DoWork function:

void gapBW_DoWork(object sender, DoWorkEventArgs e) {
    try {
        Report aReport = e.Argument as Report;
        aReport.Initialize();   // throws SQL exceptions
        aReport.GenerateData(); // throws IO file exceptions
    }
    catch (Exception except) {
        e.Cancel = true;    
        e.Result = except.Message.ToString();
    }
}

void gapBW_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {   
    if (e.Error != null) {  // I can't get this to trigger, How does this error get set?
        MessageBox.Show("Error: " + (e.Error as Exception).ToString());
    }
    else if (e.Cancelled) {
        MessageBox.Show("Canceled: " + (e.Result).ToString());
    }
    else {
        MessageBox.Show("Success");
    }
}

I get a TargetInvocationException was unhandled in Program.cs at the automatically generated Application.Run(new ReportingService()); line. I placed a breakpoint on my RunWorkerCompleted and can see that e.Cancelled = true, e.Error = null, and e.UserState = null. The message contained within e.Cancelled is simply "Operation has been cancelled". I imagine I'm receiving the TargetInvocationException from an invalid cast of e.Result (since it's null). What I want to know though, is how come e.Error is still null and e.Canceled doesn't contain any helpful information about why the operation was canceled?

3. When I tried setting e.Canceled = true; from within DoWork on an exception catch, I managed to trigger the else if (e.Cancelled) { line in my RunWorkerCompleted function. I thought this was reserved for the user requesting the job being canceled though? Am I fundamentally misunderstanding how the background worker functions?

jdylanmc
  • 829
  • 11
  • 24

4 Answers4

2

You're on the right track. In the RunWorkerCompleted event, the e.Error argument contains any exception that was thrown. In this case, you should treat your

if (e.Error != null) {...}

as the catch block of a try that you ran your background worker in, if that makes sense.

Thebigcheeze
  • 3,408
  • 2
  • 22
  • 18
  • So I would not need a try/catch around my SQL query inside of my Report class? When I remove it, I get a SqlException was unhandled by user code error. The exception never travels up to my my worker's work complete? – jdylanmc Aug 19 '11 at 13:51
  • 1
    If you are debugging, just press continue (F5 by default) and control should flow into your RunWorkerCompleted event handler and e.Error should be populated. – Thebigcheeze Jan 18 '12 at 16:56
2

I tried this little test program and it works as expected:

static void Main(string[] args)
{
    var worker = new BackgroundWorker();

    worker.DoWork += (sender, e) => { throw new ArgumentException(); };
    worker.RunWorkerCompleted += (sender, e) => Console.WriteLine(e.Error.Message);
    worker.RunWorkerAsync();

    Console.ReadKey();
}

But when i run this programm within the debugger i also got the message about an unhandled exception at the throw-statement. But i simply pressed F5 again and it continued without any problems.

Oliver
  • 43,366
  • 8
  • 94
  • 151
  • Thanks everyone for your help, but this ultimately fixed my problem. Turns out the issue wasn't my misunderstanding of C#, but my misunderstanding of Visual Studio. I had gotten into the habit of pressing F5 to run my program, and thought that when an exception was unhandled the program would crash. I didn't realize you could press F5 again to continue processing. – jdylanmc Aug 19 '11 at 15:23
  • Also don't forget the ability to press F10, F11 and Shift-F11. ;-)) – Oliver Aug 19 '11 at 15:34
  • Maybe a look into [this question](http://stackoverflow.com/questions/1044460/unhandled-exceptions-in-backgroundworker/1044610#1044610) had also helped you out. – Oliver Aug 19 '11 at 15:39
0

Set e.Cancel = true in the catch block on the doEvent if any error occur. Set WorkerSupportsCancellation Property true first.

In the DoWork event.

private void bw_DoWork( object sender, DoWorkEventArgs e )
    {
        try
        { 
            if( !backgroundWorkder.CancellationPending )
            {
               //  ....
            }
         }
         catch
         {
             if (bgWorker.WorkerSupportsCancellation && !bWorker.CancellationPending)
             {
                 e.Cancel = true;
                 e.Result = "Give your error";
                 return;
             }
         }
   }

In OnRunWorkerCompleted Method.

private void BW_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e )
    {

     if( e.Cancelled )
        {
            MessageBox.Show( e.Result.ToString());
            return;
        }
}

If you do not do any exception handling in the DoEvent. BackgroundWorker itself do this for you.

If an exception is raised during an asynchronous operation, the class will assign the exception to the Error property. The client application's event-handler delegate should check the Error property before accessing any properties in a class derived from AsyncCompletedEventArgs; otherwise, the property will raise a TargetInvocationException with its InnerException property holding a reference to Error.

The value of the Error property is null if the operation was canceled.

In that case.

private void bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    // First, handle the case where an exception was thrown.
    if (e.Error != null)
    {
        MessageBox.Show(e.Error.Message);
    }
    else if (e.Cancelled)
    {
        // Next, handle the case where the user canceled the operation.         
    }
}

See here for more details.

CharithJ
  • 46,289
  • 20
  • 116
  • 131
0

RunWorkerCompleted is fired every time when operation in DoWork is finished, cancelled or throwing an exception. Then in RunWorkerCompleted you check RunWorkerCompletedEventArgs if Error is not null. Error property is automatically set when exception in DoWork occurs. No need for try-catch in this particular case.

You are on right track.

Tomas Voracek
  • 5,886
  • 1
  • 25
  • 41