locked
RunWorkerCompleted not capturing exceptions RRS feed

  • Question

  • I have  a class I created to connect to, authorize and download files. The class is created and called by the UI thread. Inside the class I created a background worker and tell it to start. In the DoWork delegate I call several other methods, etc. Inside of those methods I have tried many things and currently I am telling them to throw their exceptions, so that it always chains back up to the DoWork delegate. I tried not having a catch at all in the DoWork delegate and code execution stops on whatever method threw the exceptions, catch code. I have also tried throwing the exception in two ways in the DoWork delegate (catch throw; and catch (Exception ex) throw ex;) and it stops on the catch code in the DoWork delegate. Once the exception occurs and code execution stops, if I hit continue it will then execute the RunWorkerCompleted event. This seems counterintuitive. I want the RunWorkerCompleted event to handle the error, but if code execution stops this will screw with the production version of my app :(. How do you make it so it JUST runs RunWorkerCompleted and doesn't choke on the inital exception?

    Please help.

    Phylum
    Thursday, September 10, 2009 4:10 PM

Answers

  • Yep.  The execution stopped.  Try running the application under "real-world" conditions.  Press Ctrl+F5 to run the app.  You're not going to have a debugger attached in the real world, and I anticipate that's your entire problem.


    Coding Light - Illuminated Ideas and Algorithms in Software
    Coding Light WikiLinkedInForumsBrowser
    Thursday, September 10, 2009 5:50 PM
    Moderator
  • I'm still voting that the debugger is set up to stop on certain exceptions.
    Well, it is. The message loop has awareness that the debugger is attached so that it will stop when an event handler throws an exception.  In general, it will always stop when there's nobody to catch the exception.  No such awareness is built into BackgroundWorker.  The code looks roughly like this:

      Exception error = null;
      try {
        OnDoWork();
      }
      catch (Exception ex) {
        error = ex;
      }

    The catch clause ensures that all exceptions raised by DoWork are caught and passed to the RunWorkerCompleted event handler as the e.Error property.  This corresponds with what I see when I try it, the debugger does not stop on the exception.  Hard to see how this would work otherwise for anybody else, except if the Debug + Exceptions, Thrown checkbox is turned on.

    Hans Passant.
    Friday, September 11, 2009 4:45 PM
    Moderator

All replies

  • It should.

    Let's start simple.

    static void Main()
    {
        BackgroundWorker wrk = new BackgroundWorker();
        wrk.DoWork += new DoWorkEventHandler(wrk_DoWork);
        wrk.RunWorkerCompleted += new RunWorkerCompletedEventHandler(wrk_RunWorkerCompleted);
        wrk.RunWorkerAsync();
        Thread.Sleep(5000);
    }
    
    static void wrk_DoWork(object sender, DoWorkEventArgs e)
    {
        throw new NotImplementedException();
    }
    
    static void wrk_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        System.Windows.Forms.MessageBox.Show("Completed");
    }

    This works for me.
    Thursday, September 10, 2009 4:25 PM
  • Try it from a class initiated by the UI thread. And then make the exception come from a method the DOWork delegate calls.
    Thursday, September 10, 2009 5:44 PM
  • Try it from a class initiated by the UI thread. And then make the exception come from a method the DOWork delegate calls.

    "If the operation raises an exception that your code does not handle, the BackgroundWorker catches the exception and passes it into the RunWorkerCompleted event handler,..."

    Review the MSDN Documentation for BackgroundWorker.

    おろ?
    Thursday, September 10, 2009 5:49 PM
  • Yep.  The execution stopped.  Try running the application under "real-world" conditions.  Press Ctrl+F5 to run the app.  You're not going to have a debugger attached in the real world, and I anticipate that's your entire problem.


    Coding Light - Illuminated Ideas and Algorithms in Software
    Coding Light WikiLinkedInForumsBrowser
    Thursday, September 10, 2009 5:50 PM
    Moderator
  • The debugger should not stop, there's a catch handler in BackgroundWorker.  Verify your settings: Debug + Exceptions, make sure the Thrown check boxes are off.

    You must check the e.Error property in your RunWorkerCompleted event handler.

    Hans Passant.
    Thursday, September 10, 2009 9:46 PM
    Moderator
  • The debugger should not stop, there's a catch handler in BackgroundWorker.  Verify your settings: Debug + Exceptions, make sure the Thrown check boxes are off.

    You must check the e.Error property in your RunWorkerCompleted event handler.

    Hans Passant.

    The property is derived form System.Exception and you need to check if for null...if it is null then no exception occured.  I

    If an exception occured the Property will be the same type as what you see inside of your Try/Catch block within DoWork.

    Here's some test code.

    ·                                  

        class BGWtest

        {

            BackgroundWorker worker;

            public BGWtest()

            {

                InitializeWorker();

                return;

            }

     

            public void Run(object argument)

            {

                this.worker.RunWorkerAsync(argument);

            }

           

            private void InitializeWorker()

            {

                this.worker = new BackgroundWorker();

                this.worker.WorkerReportsProgress = true;

                this.worker.WorkerSupportsCancellation = true;

                //

                //

                this.worker.RunWorkerCompleted +=

                    new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);

                this.worker.ProgressChanged +=

                    new ProgressChangedEventHandler(worker_ProgressChanged);

                this.worker.Disposed +=

                    new EventHandler(worker_Disposed);

                this.worker.DoWork +=

                    new DoWorkEventHandler(worker_DoWork);

            }

     

            void worker_DoWork(object sender, DoWorkEventArgs e)

            {

                if(!worker.CancellationPending)

                {

                    BackgroundWorkerData argument = (BackgroundWorkerData)e.Argument;

                    try

                    {

                        int data = 1 / argument.Data;  // exception caught below

                        int num = 1;

                    }

                    catch (Exception ex)

                    {

                        Debug.WriteLine("Caught exception inside of DoWork.");

                        //throw ex;

                        e.Result = ex;

                        //e.Cancel = true;

                    }

                    //int dataObject = 1 / argument.Data; // unhandled exception thrown here

                }

                return;

            }

     

            void worker_Disposed(object sender, EventArgs e)

            {

                return;

            }

     

            void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)

            {

                return;

            }

     

            void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)

            {

                // First, handle the case where an exception was thrown.

                if (e.Error != null)

                {

                    Debug.WriteLine("RunWorkerCompleted: e.Error: " + e.Error.Message);

                }

                else if (e.Cancelled)

                {

                    // Next, handle the case where the user canceled

                    // the operation.

                    // Note that due to a race condition in

                    // the DoWork event handler, the Cancelled

                    // flag may not have been set, even though

                    // CancelAsync was called.

                    Debug.WriteLine("RunWorkerCompleted: e.Cancelled: " + "Canceled");

                }

                else

                {

                    // Finally, handle the case where the operation

                    // succeeded.

                    Debug.WriteLine("RunWorkerCompleted: Finally: " + e.Result.ToString());

                }

     

                return;

            }

        }

        class BackgroundWorkerData

        {

            private int data;

            public int Data

            {

                get

                {

                    return data;

                }

                set

                {

                    data = value;

                }

            }

        }

     
    Note the commented line.... throw ex;


    Mark the best replies as answers. "Fooling computers since 1971."
    Thursday, September 10, 2009 9:55 PM
    Moderator
  • Somebody has wrote an article concerning the difficulties involved with exceptions and their handling in BackgroundWorker.  Specifically, when and how to use try/catch and how errors "bubble up" from DoWork().

    おろ?
    Friday, September 11, 2009 1:52 PM
  • I think the author of that article didn't bother to read the documentation on the RunWorkerCompleted event.
    My code sample contains the identical code from the example at the link.
    Mark the best replies as answers. "Fooling computers since 1971."
    Friday, September 11, 2009 2:17 PM
    Moderator
  • RudeDog2, your code is very useful and shows the idea programatically.  I posted the article to give a little background on the "why's" behind your design.  The author posts the MSDN article verbatim "BackgroundWorker catches the exception and passes it into the RunWorkerCompleted event handler" and goes a step further to explain how wrapping code within DoWork() within a Try/Catch block will cause the exception to be handled and therefore not unhandled and sent inside RunWorkerCompletedEventArgs .  You stepped around this problem by assigning the exception explicitly (e.Result = ex), which I do not see a problem with.  I just want the OP to understand why you did this and also that they don't HAVE to do this if they would rather remove Try/Catch within DoWork() and let the event pass naturally to RunWorkerCompletedEventArgs .

    おろ?
    Friday, September 11, 2009 2:43 PM
  • I'm still voting that the debugger is set up to stop on certain exceptions.  I commonly do this to track down specific errors in my code, then forget to turn it off. 
    Coding Light - Illuminated Ideas and Algorithms in Software
    Coding Light WikiLinkedInForumsBrowser
    Friday, September 11, 2009 2:45 PM
    Moderator
  • The Debugger stops on the exception within DoWork. 
    I made a recent post about this exact issue. 

    I had been under the assumption that the Debugger would not stop on DoWork exceptions, but---surprise, surprise-- it does halt and report the exception.  And when the Debugger reports the exception, the exception does not get assigned to the Error property in RunWorkerComptedEventArgs.

    However, you can press F5 and continue execution without any hiccups it seems. 
    I assume the BGW exception mechanism must kick in to allow execution to continue, but it should still set the Error property.

    I was using the Result property to compare the behaviors by running code with and without the Debugger.

    I am voting that the Debugger will stop on all exceptions within DoWork.  Test the code without it.

    Rudedog   =8^D

    Mark the best replies as answers. "Fooling computers since 1971."
    Friday, September 11, 2009 3:00 PM
    Moderator
  • I'm still voting that the debugger is set up to stop on certain exceptions.
    Well, it is. The message loop has awareness that the debugger is attached so that it will stop when an event handler throws an exception.  In general, it will always stop when there's nobody to catch the exception.  No such awareness is built into BackgroundWorker.  The code looks roughly like this:

      Exception error = null;
      try {
        OnDoWork();
      }
      catch (Exception ex) {
        error = ex;
      }

    The catch clause ensures that all exceptions raised by DoWork are caught and passed to the RunWorkerCompleted event handler as the e.Error property.  This corresponds with what I see when I try it, the debugger does not stop on the exception.  Hard to see how this would work otherwise for anybody else, except if the Debug + Exceptions, Thrown checkbox is turned on.

    Hans Passant.
    Friday, September 11, 2009 4:45 PM
    Moderator
  • Well, the person that told me to reread the documentation was correct, I missed something. Here is a quote:

    If the operation raises an exception that your code does not handle, the BackgroundWorker catches the exception and passes it into the RunWorkerCompleted event handler, where it is exposed as the Error property of System.ComponentModel..::.RunWorkerCompletedEventArgs. If you are running under the Visual Studio debugger, the debugger will break at the point in the DoWork event handler where the unhandled exception was raised.

    Since I am in debug mode the exception will stop code exectuion, but allow you to resume, passing the error to the RunWorkerCompleted event. If I run in release mode it will ignore the error and simply just pass it to the RunWorkerCompleted event as documented. All is well. Thanks for your help and interest.

    Phylum

    Friday, September 11, 2009 4:46 PM