none
Strange: calling EndInvoke from an AsyncCallback - why UnhandledException

    Question

  • Can someone help me explain this? In the simple example below, why does case 4 cause AppDomain.UnhandledException (which kills the app) instead of Application.ThreadException, given that the exception is ultimately thrown on the UI thread (being the same thread on which the exception is thrown in cases 1-3)? What am I missing?

     

    using System;

    using System.Collections.Generic;

    using System.ComponentModel;

    using System.Data;

    using System.Drawing;

    using System.Text;

    using System.Windows.Forms;

    using System.Threading;

    namespace WindowsApplication4 {

    public partial class Form1 : Form {

    private delegate void CrasherDelegate();

    public Form1() {

    InitializeComponent();

    AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(currentDomain_UnhandledException);

    System.Windows.Forms.Application.ThreadException += new ThreadExceptionEventHandler(application_ThreadException);

    }

    private void currentDomain_UnhandledException(object source, UnhandledExceptionEventArgs e) {

    Exception ex = (Exception) e.ExceptionObject;

    label1.Text = "There was a problem (Unhandled Exception - Terminating: " + e.IsTerminating + ")\n" + ex.Message;

    MessageBox.Show(this, ex.Message, "There was a problem (Unhandled Exception - Terminating: " + e.IsTerminating + ")", MessageBoxButtons.OK, MessageBoxIcon.Error);

    }

    private void application_ThreadException(object source, ThreadExceptionEventArgs e) {

    Exception ex = e.Exception;

    label1.Text = "There was a problem (Thread Exception)\n" + ex.Message;

    MessageBox.Show(this, ex.Message, "There was a problem (Thread Exception)", MessageBoxButtons.OK, MessageBoxIcon.Error);

    }

    private void Crasher() {

    throw new ArgumentException("Crasher threw on thread: " + Thread.CurrentThread.ManagedThreadId);

    }

    private void CrasherWithParam(object param) {

    throw new ArgumentException("Crasher(" + param + ") threw on thread: " + Thread.CurrentThread.ManagedThreadId);

    }

    private void Callback(IAsyncResult iar) {

    if (!this.InvokeRequired) {

    CrasherDelegate d = iar.AsyncState as CrasherDelegate;

    if (d != null) {

    MessageBox.Show("Calling EndInvoke on thread: " + Thread.CurrentThread.ManagedThreadId);

    d.EndInvoke(iar);

    }

    }

    else {

    this.Invoke((MethodInvoker) delegate {

    this.Callback(iar);

    });

    }

    }

    private void button1_Click(object sender, EventArgs e) {

    Crasher();

    }

    private void button2_Click(object sender, EventArgs e) {

    CrasherDelegate d = new CrasherDelegate(Crasher);

    d.Invoke();

    }

    private void button3_Click(object sender, EventArgs e) {

    CrasherDelegate d = new CrasherDelegate(Crasher);

    IAsyncResult iar = d.BeginInvoke(null, d);

    // both of these work equally well

    d.EndInvoke(iar);

    //Callback(iar);

    }

    private void button4_Click(object sender, EventArgs e) {

    CrasherDelegate d = new CrasherDelegate(Crasher);

    AsyncCallback cb = new AsyncCallback(Callback);

    IAsyncResult iar = d.BeginInvoke(cb, d);

    }

    private void button5_Click(object sender, EventArgs e) {

    WaitCallback cb = new WaitCallback(CrasherWithParam);

    ThreadPool.QueueUserWorkItem(cb);

    }

    }

    }

    Thursday, April 12, 2007 8:09 PM

All replies

  • As far as I know the callback is executed in the thread pool thread that the invoked function was executed. This is not the UI thread where you receive the callback. The other examples 1,2 execute synchronously on the same thread. 3 will delay the exception throwing and "marhsal" the exception to the EndInvoke call into your thread.
    4 will definitely be executed from another thread for performance reasons since the call is asynchronous there is no reason to execute the callback (again) in another thread. The CLR simply reuses the thread for the callback where you get the exception then.
    I think 5 should also terminate your application but have not tried it yet. Perhaps you have to wait a little until the thread starts running. ...

    Yours,
       Alois Kraus

    Friday, April 13, 2007 12:25 AM
  • Yes, the callback is executed on the worker thread (#3), but inside the callback a check is made (InvokeRequired) to ensure that the EndInvoke is marshalled to the UI thread #1 (and the MessageBox confirms that), so EndInvoke is being called on the UI thread and not the worker thread, just as in cases 1, 2 and 3. The callback is not really being called twice -- it's simply being forwarded to the UI thread, where it must be run if it updates any UI component. I see no reason why only in this case it results in an UnhandledException. I would expect a ThreadException, consistent with the first 3 cases.

     

    And, yes, 5 does also terminate the app with an UnhandledException, but I'd expect that in this case because there is no way that the exception could be thrown on the UI thread.

    Friday, April 13, 2007 1:02 AM
  • The problem you encounter here is that the CLR will rethrow the exception inside the callback when you call EndInvoke. But it does only check if you do call EndInvoke on the same thread the callback executes. Since you call EndInvoke ulitmately from an arbitrary thread the CLR thinks your callback did not handle the exception and wil rethrow the exception when your callback is left.

     

    Yours,

       Alois Kraus

     

    Friday, April 13, 2007 7:39 AM
  • I'm sorry, I've reread this several times and I don't fully understand what you're trying to say.

     

     Alois wrote:

    The problem you encounter here is that the CLR will rethrow the exception inside the callback when you call EndInvoke.

     

    Yes, I'm aware of that. I expect EndInvoke to throw to signal the fact that the asynchronous method threw, but I expect it to throw Application.ThreadException, not AppDomain.UnhandledException, since it's running on the UI thread.

     

     Alois wrote:

     But it does only check if you do call EndInvoke on the same thread the callback executes.

     

    I'm not sure what check you're referring to.

     

     Alois wrote:

     Since you call EndInvoke ulitmately from an arbitrary thread...

     

    Not from an arbirary thread, from the thread that controls the form's underlying window handle, which is the UI thread, same as in cases 1-3.

     

     Alois wrote:

    ...the CLR thinks your callback did not handle the exception...

     

    Well, it deliberately did not handle the exception because I want it to throw ThreadException. I'm not sure what the point you're making here is. Even if I handle the exception in the callback and rethrow it, or throw a completely new exception, it still results in an UnhandledException.

     

     Alois wrote:

    ...and wil rethrow the exception when your callback is left.

     

    But the callback never completes -- it's the actual EndInvoke that terminates the app.

     

     

    Have I misunderstood what you were trying to say?

     

    It's not that an exception is ultimately being thrown by EndInvoke and caught by the global handlers that bothers me -- I'm expecting that. What I don't understand is why the exception is an UnhandledException (that kills the app) and not just a ThreadException (that does not). The original exception was not a fatal error, yet it's impossible to prevent the app from dying without handling the exception inside the asynchronous callback. If you have hundreds of asynchronous callbacks in your application, you'd have to replicate your application_ThreadException code in every one of those. I can certainly do that, but I'd like to understand why I need to first. It makes a mockery of a global exception handler, and in my case could provide other problems, since my async methods are scattered across a dozen or so UI components.

    Friday, April 13, 2007 1:44 PM
  • I actually have a way to avoid replicating the global exception handling code, described below in case 6, but I think this is a ludricrous work-around, and highlights why I think the CLR's behaviour is inconsistent:

     

    ...

    private Exception callbackException;

    private Exception CallbackException {

    set {

    callbackException = value;

    timer1.Enabled = true;

    }

    }

    ...

    private void CallbackMarshalToForm(IAsyncResult iar) {

    if (!this.InvokeRequired) {

    CrasherDelegate d = iar.AsyncState as CrasherDelegate;

    if (d != null) {

    try {

    d.EndInvoke(iar);

    }

    catch (Exception e) {

    CallbackException = e;

    }

    }

    }

    else {

    this.Invoke((MethodInvoker) delegate {

    this.CallbackMarshalToForm(iar);

    });

    }

    }

    ...

    private void button6_Click(object sender, EventArgs e) {

    CrasherDelegate d = new CrasherDelegate(Crasher);

    AsyncCallback cb = new AsyncCallback(CallbackMarshalToForm);

    IAsyncResult iar = d.BeginInvoke(cb, d);

    }

    private void timer1_Tick(object sender, EventArgs e) {

    timer1.Enabled = false;

    throw callbackException;

    }

    }

    }

     

     

    The gist of this is to catch the exception in the callback, start a timer and then re-throw the exception in the timer's first Tick. This works exactly as I'd expect it to.

    It appears that the problem lies in how the framework decides between AppDomain.UnhandledException and Application.ThreadException for unhandled exceptions. Whatever that logic is, it would seem to be more geared towards inspecting the stack to see if we're throwing inside an asynchronous callback, rather than what thread it is we're in right now. Could it be that any exception thrown inside an asynchronous callback -- regardless of which thread that callback is running on, results in an app-terminating UnhandledException? I'd really like to know why that needs to be the case.

    Friday, April 13, 2007 2:12 PM
  • wbradney, The call to EndInvoke will rethrow the exception cached from the asynchronous delegate call.  Since you're using Control.InvokeRequired/Control.Invoke to ensure EndInvoke is called on the UI thread the exception is thrown on the main thread (hence not a ThreadException event) at a point where you have no try/catch block surrounding it, ergo it's unhandled.

     

    The only place you can catch this type of exception is by wrapping the call to Show[Dialog] or Application.Run(myFormObject).

    Friday, April 13, 2007 8:41 PM
    Moderator
  • Peter,

     

    I still don't see why that's any different from cases 1-3, where the the exception is also thrown on the main thread, with no try/catch block. Those are caught as ThreadExceptions by the static handler, and the application continues. Why aren't those also UnhandledExceptions that kill the app? In my example, in ALL cases the exceptions are unhandled, but they are caught by different global exception handlers and have different effects on the life of the app -- all I'm trying to figure out is WHY.

     

    And catching the exception in the main method (try..catch around Application.Run) still won't prevent the application from dying (or in this case becoming unusable/invisible).

     

    Regards,

    WMB

    Saturday, April 14, 2007 2:14 PM
  • When you subscribe to the ThreadException event, you're subscribing to that event for the thread in which it was called; you're supposed to get this event for any events thrown from code called from that thread.  Because a thread pool thread was used (via BeginInvoke) to call the Control.Invoke that marshaled the code to the GUI thread, the thread that the CLR sees as active is the thread pool thread.  Remember, the thread pool thread is blocked on the Control.Invoke call, the CLR has to limit that sort of thing because you're in a race condition: the thread pool thread is blocked without using locking/synchronization on another thread and they could be dependant on one another.

     

    Because the exception is viewed as being throw on a background thread, it doesn't terminate the app.

    Saturday, April 14, 2007 2:36 PM
    Moderator
  •  Peter Ritchie wrote:

    Since you're using Control.InvokeRequired/Control.Invoke to ensure EndInvoke is called on the UI thread the exception is thrown on the main thread (hence not a ThreadException event) at a point where you have no try/catch block surrounding it, ergo it's unhandled.

     

    Also, Peter, something else confuses me about this statement. What about this new case:

     

    private void CallbackNoInvoke(IAsyncResult iar) {

    CrasherDelegate d = iar.AsyncState as CrasherDelegate;

    if (d != null) {

    MessageBox.Show("Calling EndInvoke2 on thread: " + Thread.CurrentThread.ManagedThreadId);

    d.EndInvoke(iar);

    MessageBox.Show("Called EndInvoke");

    }

    }

    private void button7_Click(object sender, EventArgs e) {

    CrasherDelegate d = new CrasherDelegate(Crasher);

    AsyncCallback cb = new AsyncCallback(CallbackNoInvoke);

    IAsyncResult iar = d.BeginInvoke(cb, d);

    }

    }

     

     

     

    Now there's no Control.InvokeRequired or Invoke, and EndInvoke is NOT called on the UI thread, but the worker thread. Does this mean ergo it SHOULD be a ThreadException and NOT an UnhandledException? Of course, it does not -- in fact this case has the exact same outcome as case 4 - an UnhandledException that kills the app.

     

    Please excuse me if I'm being really dense about something here, but this seems to me to be a significant inconsistency. Async callback exceptions are being treated with NO regard as to which thread they are ultimately rethrown on for the purposes of deciding what is or is not an app-killing UnhandledException.

     

    I've actually (grudgingly) implemented my timer-based workaround in my real application, but it strikes me as very odd that this should be required. I'd ask you to please take another close look at all the cases in my example form and tell me you think there isn't an inconsistency.

    Saturday, April 14, 2007 2:42 PM
  •  Peter Ritchie wrote:

    When you subscribe to the ThreadException event, you're subscribing to that event for the thread in which it was called; you're supposed to get this event for any events thrown from code called from that thread. 

     

    So, how does one go about registering a ThreadException handler for a thread pool thread? In my example, registering a handler inside Crasher() makes no difference to the outcome of case 4.

     

     Peter Ritchie wrote:
     

     Because a thread pool thread was used (via BeginInvoke) to call the Control.Invoke that marshaled the code to the GUI thread, the thread that the CLR sees as active is the thread pool thread.

     

    Agreed, although the active thread at this point is ACTUALLY the UI thread, and I don't see why the CLR doesn't want to see it that way.

     

     Peter Ritchie wrote:

    Because the exception is viewed as being throw on a background thread, it doesn't terminate the app.

     

    You've got me confused here. Case 4 DOES terminate the app. Presumably it does so because there is no ThreadException handler for the thread pool thread that the CLR sees as the active thread, as per your first and second quotes above.
    Saturday, April 14, 2007 3:14 PM
  • The problem with registering a ThreadException is that event signals "an otherwise unhandled exception"; so you've got a catch-22.  If it's unhandled your thread-pool is recycled as soon as the exception would otherwise escape your thread method; and if you catch the exception before leaving your method the exception is non longer unhandled.

     

    I gather from the plethora of different techniques you have in your example you're learning about these various multithreading techniques and how the compare.  My recommendations are to avoid Control.Invoke and prefer Control.BeginInvoke when you can; and prefer using BackgroundWorker for UI responsiveness.  If you have no need to block when you marshal a call to the GUI thread, just use Control.BeginInvoke (this is ideal when all the state needed is being passed as arguments; sometimes less ideal when you're invoking a method that gets is state from instance members).  Control.Invoke can get you in trouble in so many different ways.  You can use an asynchronous delegate to perform a lengthy operation to avoid blocking the GUI thread; but, using BackgroundWorker makes life so much easier--especially when you need to know when the operation completes (e.g. controls need to be disabled while it's running) or you need to display progress.

    Saturday, April 14, 2007 3:38 PM
    Moderator
  •  wbradney wrote:
     Peter Ritchie wrote:

    Since you're using Control.InvokeRequired/Control.Invoke to ensure EndInvoke is called on the UI thread the exception is thrown on the main thread (hence not a ThreadException event) at a point where you have no try/catch block surrounding it, ergo it's unhandled.

     

    Also, Peter, something else confuses me about this statement. What about this new case:

     

    private void CallbackNoInvoke(IAsyncResult iar) {

    CrasherDelegate d = iar.AsyncState as CrasherDelegate;

    if (d != null) {

    MessageBox.Show("Calling EndInvoke2 on thread: " + Thread.CurrentThread.ManagedThreadId);

    d.EndInvoke(iar);

    MessageBox.Show("Called EndInvoke");

    }

    }

    private void button7_Click(object sender, EventArgs e) {

    CrasherDelegate d = new CrasherDelegate(Crasher);

    AsyncCallback cb = new AsyncCallback(CallbackNoInvoke);

    IAsyncResult iar = d.BeginInvoke(cb, d);

    }

    }

     

     

     

    Now there's no Control.InvokeRequired or Invoke, and EndInvoke is NOT called on the UI thread, but the worker thread. Does this mean ergo it SHOULD be a ThreadException and NOT an UnhandledException? Of course, it does not -- in fact this case has the exact same outcome as case 4 - an UnhandledException that kills the app.

    Since this is again throwing an exception in a background thread (thread pool thread) with no ThreadException handler or try/catch block it causes the UnhandledException event.

    I'm unsure why that particular case causes the application to terminate whereas introducing Control.Invoke would not.  I know that Control.Invoke does do it's own exception handling while calling the delegate; but I would have expected unhandled exceptions to be handled in the same way (for lack of better term :-).  I'd have to dig into that a bit further.

     wbradney wrote:
      

    Please excuse me if I'm being really dense about something here, but this seems to me to be a significant inconsistency. Async callback exceptions are being treated with NO regard as to which thread they are ultimately rethrown on for the purposes of deciding what is or is not an app-killing UnhandledException.

     

    I've actually (grudgingly) implemented my timer-based workaround in my real application, but it strikes me as very odd that this should be required. I'd ask you to please take another close look at all the cases in my example form and tell me you think there isn't an inconsistency.

    Maybe if you explain at a higher level what you're trying to accomplish I can offer some more detailed suggestions.

     

    I'm getting the impression, in the face of background threads, you're either attempting to give a better user experience by not allowing the CLR unhandled exception form to appear, or you're attempting to keep the application running in the face of unhandled exceptions?  Can you explain what you're using background threads for, is it just to keep your UI responsive, or is it more complex?

    Saturday, April 14, 2007 3:52 PM
    Moderator
  •  Peter Ritchie wrote:

    I'm unsure why that particular case causes the application to terminate whereas introducing Control.Invoke would not.  I know that Control.Invoke does do it's own exception handling while calling the delegate; but I would have expected unhandled exceptions to be handled in the same way (for lack of better term :-).  I'd have to dig into that a bit further.

     

    No, no - the Control.Invoke case (case 4) ALSO terminates the app -- that's what I did not understand. 

     

    Based on your previous response I added case 8:

     

    private void CallbackFireAndForgetMarshalling(IAsyncResult iar) {

    if (!this.InvokeRequired) {

    CrasherDelegate d = iar.AsyncState as CrasherDelegate;

    if (d != null) {

    MessageBox.Show("Calling EndInvoke on thread: " + Thread.CurrentThread.ManagedThreadId);

    d.EndInvoke(iar);

    }

    }

    else {

    this.BeginInvoke((MethodInvoker) delegate {

    this.Callback(iar);

    });

    }

    }

    private void button8_Click(object sender, EventArgs e) {

    // call the crasher by asynchronous invoke of a delegate (on a thread pool thread), and specify a callback that marshals the EndInvoke to the UI thread by Invoking the Form

    // result: the ThreadException handler will catch the exception and the app will continue

    CrasherDelegate d = new CrasherDelegate(Crasher);

    AsyncCallback cb = new AsyncCallback(CallbackFireAndForgetMarshalling);

    IAsyncResult iar = d.BeginInvoke(cb, d);

    }

     

    and was surprised to find that it actually works the way I would expect (a ThreadException that is handled by the global exception handler and the app continues). So the answer, as you suggested would seem to be to use Control.BeginInvoke rather than Control.Invoke inside AsyncCallbacks. It's the fire-and-forget nature of the callback marshalling in case 8 that makes all the difference, it seems.

     

    The BackgroundWorker case also works as expected, so in this case I assume that it just uses an underlying BeginInvoke strategy for the AsyncCallback:

     

    private void button9_Click(object sender, EventArgs e) {

    // call the crasher by using a BackgroundWorker

    // result: the ThreadException handler will catch the exception and the app will continue

    BackgroundWorker worker = new BackgroundWorker();

    worker.DoWork += new DoWorkEventHandler(worker_DoWork);

    worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);

    worker.RunWorkerAsync();

    }

    private void worker_DoWork(object sender, DoWorkEventArgs e) {

    Crasher();

    }

    private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {

    if (e.Error != null) {

    throw e.Error;

    }

    }

     

     Peter Ritchie wrote:

    I'm getting the impression, in the face of background threads, you're either attempting to give a better user experience by not allowing the CLR unhandled exception form to appear, or you're attempting to keep the application running in the face of unhandled exceptions?  Can you explain what you're using background threads for, is it just to keep your UI responsive, or is it more complex?

     

    Actually both. I need to invoke long-running operations in the background for feedback and perceived performance, but any exceptions thrown by those operations should be handled the same way as exceptions throws during synchronous operations (ie. globally handled by the ThreadException handler and presented to the user). They should certainly NOT crash the app, since they're not in way fatal exceptions. I also don't want to have to have the programmers coding these long-running operations care one bit about whether or not they are running synchronously or asynchronously, so I can't tell them "Whatever you do, make sure your method doesn't throw any exceptions because they'll kill the app". In fact I WANT them to throw appropriately, so that the app can notifiy the user appropriately.

     

    I dislike BackgroundWorker for the same reasons -- it means that operations have to be CODED with background invocation in mind, and I don't think that's a good thing.

     

    So at the end of the day I have a better answer than to use a timer (ie. case 8, fire-and-forget UI marshalling), so I'm happy. I still have a nagging feeling that something is not right with that case 4, though...

     

    Thanks for your help,

    WMB

    Saturday, April 14, 2007 4:38 PM
  • For posterity I'll post the full set of test cases with commentary:

     

    Code Snippet

    using System;

    using System.Collections.Generic;

    using System.ComponentModel;

    using System.Data;

    using System.Drawing;

    using System.Text;

    using System.Windows.Forms;

    using System.Threading;

     

    namespace WindowsApplication4 {

    public partial class Form1 : Form {

    private ThreadExceptionEventHandler threadExceptionHandler;

    private delegate void CrasherDelegate();

     

    private Exception callbackException;

    private Exception CallbackException {

    set {

    callbackException = value;

    timer1.Enabled = true;

    }

    }

     

    public Form1() {

    InitializeComponent();

    threadExceptionHandler = new ThreadExceptionEventHandler(application_ThreadException);

    AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(currentDomain_UnhandledException);

    System.Windows.Forms.Application.ThreadException += threadExceptionHandler;

    }

     

    private void currentDomain_UnhandledException(object source, UnhandledExceptionEventArgs e) {

    Exception ex = (Exception) e.ExceptionObject;

    label1.Text = "There was a problem (Unhandled Exception - Terminating: " + e.IsTerminating + ")\n" + ex.Message;

    MessageBox.Show(this, ex.Message, "There was a problem (Unhandled Exception - Terminating: " + e.IsTerminating + ")", MessageBoxButtons.OK, MessageBoxIcon.Error);

    }

     

    private void application_ThreadException(object source, ThreadExceptionEventArgs e) {

    Exception ex = e.Exception;

    label1.Text = "There was a problem (Thread Exception)\n" + ex.Message;

    MessageBox.Show(this, ex.Message, "There was a problem (Thread Exception)", MessageBoxButtons.OK, MessageBoxIcon.Error);

    }

     

    private void Crasher() {

    throw new ArgumentException("Crasher threw on thread: " + Thread.CurrentThread.ManagedThreadId);

    }

     

    private void CrasherWithParam(object param) {

    throw new ArgumentException("Crasher(" + param + ") threw on thread: " + Thread.CurrentThread.ManagedThreadId);

    }

     

    private void Callback(IAsyncResult iar) {

    if (!this.InvokeRequired) {

    CrasherDelegate d = iar.AsyncState as CrasherDelegate;

    if (d != null) {

    MessageBox.Show("Calling EndInvoke on thread: " + Thread.CurrentThread.ManagedThreadId);

    d.EndInvoke(iar);

    }

    }

    else {

    this.Invoke((MethodInvoker) delegate {

    this.Callback(iar);

    });

    }

    }

     

    private void CallbackMarshalToForm(IAsyncResult iar) {

    if (!this.InvokeRequired) {

    CrasherDelegate d = iar.AsyncState as CrasherDelegate;

    if (d != null) {

    try {

    MessageBox.Show("Calling EndInvoke on thread: " + Thread.CurrentThread.ManagedThreadId);

    d.EndInvoke(iar);

    }

    catch (Exception e) {

    CallbackException = e;

    }

    }

    }

    else {

    this.Invoke((MethodInvoker) delegate {

    this.CallbackMarshalToForm(iar);

    });

    }

    }

     

    private void CallbackNoInvoke(IAsyncResult iar) {

    CrasherDelegate d = iar.AsyncState as CrasherDelegate;

    if (d != null) {

    MessageBox.Show("Calling EndInvoke on thread: " + Thread.CurrentThread.ManagedThreadId);

    d.EndInvoke(iar);

    }

    }

     

    private void CallbackFireAndForgetMarshalling(IAsyncResult iar) {

    if (!this.InvokeRequired) {

    CrasherDelegate d = iar.AsyncState as CrasherDelegate;

    if (d != null) {

    MessageBox.Show("Calling EndInvoke on thread: " + Thread.CurrentThread.ManagedThreadId);

    d.EndInvoke(iar);

    }

    }

    else {

    this.BeginInvoke((MethodInvoker) delegate {

    this.Callback(iar);

    });

    }

    }

     

    private void timer1_Tick(object sender, EventArgs e) {

    timer1.Enabled = false;

    MessageBox.Show("re-throwing on thread: " + Thread.CurrentThread.ManagedThreadId);

    throw callbackException;

    }

     

    private void button1_Click(object sender, EventArgs e) {

    // call the crasher directly

    // result: the ThreadException handler will catch the exception and the app will continue

    Crasher();

    }

     

    private void button2_Click(object sender, EventArgs e) {

    // call the crasher by synchronous invoke of a delegate

    // result: the ThreadException handler will catch the exception and the app will continue

    CrasherDelegate d = new CrasherDelegate(Crasher);

    d.Invoke();

    }

     

    private void button3_Click(object sender, EventArgs e) {

    // call the crasher by asynchronous invoke of a delegate (on a thread pool thread), but block right after and wait for it to complete

    // result: the ThreadException handler will catch the exception and the app will continue

    CrasherDelegate d = new CrasherDelegate(Crasher);

    IAsyncResult iar = d.BeginInvoke(null, d);

     

    // both of these work equally well

    d.EndInvoke(iar);

    //Callback(iar);

    }

     

    private void button4_Click(object sender, EventArgs e) {

    // call the crasher by asynchronous invoke of a delegate (on a thread pool thread), and specify a callback that marshals the EndInvoke to the UI thread by Invoking the Form

    // result: the ThreadException handler does NOT catch the exception. The app will be killed by an UnhandledException

    CrasherDelegate d = new CrasherDelegate(Crasher);

    AsyncCallback cb = new AsyncCallback(Callback);

    IAsyncResult iar = d.BeginInvoke(cb, d);

    }

     

    private void button5_Click(object sender, EventArgs e) {

    // call the crasher by queueing a work item (on a thread pool thread)

    // result: the ThreadException handler does NOT catch the exception. The app will be killed by an UnhandledException

    WaitCallback cb = new WaitCallback(CrasherWithParam);

    ThreadPool.QueueUserWorkItem(cb);

    }

     

    private void button6_Click(object sender, EventArgs e) {

    // call the crasher by asynchronous invoke of a delegate (on a thread pool thread), and specify a callback that catches the exception, stores it and starts a timer that rethrows it on the first Tick

    // result: the ThreadException handler will catch the exception and the app will continue

    CrasherDelegate d = new CrasherDelegate(Crasher);

    AsyncCallback cb = new AsyncCallback(CallbackMarshalToForm);

    IAsyncResult iar = d.BeginInvoke(cb, d);

    }

     

    private void button7_Click(object sender, EventArgs e) {

    // call the crasher by asynchronous invoke of a delegate (on a thread pool thread), and specify a callback that does the EndInvoke directly on the thread pool thread

    // result: the ThreadException handler does NOT catch the exception. The app will be killed by an UnhandledException

    CrasherDelegate d = new CrasherDelegate(Crasher);

    AsyncCallback cb = new AsyncCallback(CallbackNoInvoke);

    IAsyncResult iar = d.BeginInvoke(cb, d);

    }

     

    private void button8_Click(object sender, EventArgs e) {

    // call the crasher by asynchronous invoke of a delegate (on a thread pool thread), and specify a callback that marshals the EndInvoke to the UI thread by BeginInvoking the Form

    // result: the ThreadException handler will catch the exception and the app will continue

    CrasherDelegate d = new CrasherDelegate(Crasher);

    AsyncCallback cb = new AsyncCallback(CallbackFireAndForgetMarshalling);

    IAsyncResult iar = d.BeginInvoke(cb, d);

    }

     

    private void button9_Click(object sender, EventArgs e) {

    // call the crasher by using a BackgroundWorker

    // result: the ThreadException handler will catch the exception and the app will continue

    BackgroundWorker worker = new BackgroundWorker();

    worker.DoWork += new DoWorkEventHandler(worker_DoWork);

    worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);

    worker.RunWorkerAsync();

    }

     

    private void worker_DoWork(object sender, DoWorkEventArgs e) {

    Crasher();

    }

     

    private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {

    if (e.Error != null) {

    throw e.Error;

    }

    }

    }

    }

     

    Saturday, April 14, 2007 4:53 PM
  •  wbradney wrote:
     Peter Ritchie wrote:

    I'm unsure why that particular case causes the application to terminate whereas introducing Control.Invoke would not.  I know that Control.Invoke does do it's own exception handling while calling the delegate; but I would have expected unhandled exceptions to be handled in the same way (for lack of better term :-).  I'd have to dig into that a bit further.

     

    No, no - the Control.Invoke case (case 4) ALSO terminates the app -- that's what I did not understand. 

    Yeah, you're right.  I added a catch block to see if I could catch the exception coming out of the EndInvoke and forgot to rethrow.

     wbradney wrote:

    Based on your previous response I added case 8:

     

    private void CallbackFireAndForgetMarshalling(IAsyncResult iar) {

    if (!this.InvokeRequired) {

    CrasherDelegate d = iar.AsyncState as CrasherDelegate;

    if (d != null) {

    MessageBox.Show("Calling EndInvoke on thread: " + Thread.CurrentThread.ManagedThreadId);

    d.EndInvoke(iar);

    }

    }

    else {

    this.BeginInvoke((MethodInvoker) delegate {

    this.Callback(iar);

    });

    }

    }

    private void button8_Click(object sender, EventArgs e) {

    // call the crasher by asynchronous invoke of a delegate (on a thread pool thread), and specify a callback that marshals the EndInvoke to the UI thread by Invoking the Form

    // result: the ThreadException handler will catch the exception and the app will continue

    CrasherDelegate d = new CrasherDelegate(Crasher);

    AsyncCallback cb = new AsyncCallback(CallbackFireAndForgetMarshalling);

    IAsyncResult iar = d.BeginInvoke(cb, d);

    }

     

    and was surprised to find that it actually works the way I would expect (a ThreadException that is handled by the global exception handler and the app continues). So the answer, as you suggested would seem to be to use Control.BeginInvoke rather than Control.Invoke inside AsyncCallbacks. It's the fire-and-forget nature of the callback marshalling in case 8 that makes all the difference, it seems.

     

    Right, but keep in mind because you're using Control.BeginInvoke you're running asynchronously and you will have no way to get that exception back into a higher level catch block--you'll have to handle that exception either in the ThreadException event handler or handle it directly in the code Invoked by Control.BeginInvoke.

     wbradney wrote:

    The BackgroundWorker case also works as expected, so in this case I assume that it just uses an underlying BeginInvoke strategy for the AsyncCallback:

     

    private void button9_Click(object sender, EventArgs e) {

    // call the crasher by using a BackgroundWorker

    // result: the ThreadException handler will catch the exception and the app will continue

    BackgroundWorker worker = new BackgroundWorker();

    worker.DoWork += new DoWorkEventHandler(worker_DoWork);

    worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);

    worker.RunWorkerAsync();

    }

    private void worker_DoWork(object sender, DoWorkEventArgs e) {

    Crasher();

    }

    private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {

    if (e.Error != null) {

    throw e.Error;

    }

    }

    Sort of.  Windows.Forms sets up the BackgroundWorker to default to synchronize on the GUI thread.  While internally it does that through InvokeRequired/Invoke, it deals will marshaling the exception back to your RunWorkerCompleted event handler (at least the fist one called.  OT: but I haven't tested it with multiple handlers...).

     wbradney wrote:
     

     Peter Ritchie wrote:

    I'm getting the impression, in the face of background threads, you're either attempting to give a better user experience by not allowing the CLR unhandled exception form to appear, or you're attempting to keep the application running in the face of unhandled exceptions?  Can you explain what you're using background threads for, is it just to keep your UI responsive, or is it more complex?

     

    Actually both. I need to invoke long-running operations in the background for feedback and perceived performance, but any exceptions thrown by those operations should be handled the same way as exceptions throws during synchronous operations (ie. globally handled by the ThreadException handler and presented to the user). They should certainly NOT crash the app, since they're not in way fatal exceptions. I also don't want to have to have the programmers coding these long-running operations care one bit about whether or not they are running synchronously or asynchronously, so I can't tell them "Whatever you do, make sure your method doesn't throw any exceptions because they'll kill the app". In fact I WANT them to throw appropriately, so that the app can notifiy the user appropriately.

     

    I dislike BackgroundWorker for the same reasons -- it means that operations have to be CODED with background invocation in mind, and I don't think that's a good thing.

     

    So at the end of the day I have a better answer than to use a timer (ie. case 8, fire-and-forget UI marshalling), so I'm happy. I still have a nagging feeling that something is not right with that case 4, though...

     

    Thanks for your help,

    WMB

    I don't recommend using a UnhandledException event handler as the only means of informing the user of errors (sanitizing exception messages).  As this event may be raised for exceptions thrown in background threads, you can't reliably display a message to the user.  In the case of a background thread exception, you can use MessageBox.Show, but because you're not running on the GUI thread you can't make that MessageBox a child of the main window and therefore can't ensure the MessageBox is top-most for the app--which means it could display under the main window and the user won't see it.  I also don't recommend using ThreadException on the GUI thread because it doesn't terminate your application for unhandled exceptions (unless you write the code to terminate) thrown/rethrown on the GUI thread.

     

    I do recommend using BackgroundWorker in your circumstance.  The exception marshaled back to the GUI thread (when you attempt to get the result in the RunWorkerCompleted event handler, although it's wrapped in a TargetInvocationException), I also recommend a only-catch-exceptions-you-can-handle approach and use a last-chance catch block if you don't want the CLR exception form to display (and only use the last-chance catch to display an message before terminating for unhandled exceptions).  Using these models BackgroundWorker helps to ensure that you don't get unhandled exceptions on background threads and helps eliminate your application terminating without warning because of those unhandled exceptions.

     

    Here's an example of using a last-chance catch block:

            [STAThread]

            static void Main ( )

            {

                Thread.CurrentThread.Name = "Main";

                try

                {

                    Application.EnableVisualStyles();

                    Application.SetCompatibleTextRenderingDefault(false);

                    Application.Run(new Form1());

                }

                catch (Exception exception)

                {

                    // TODO: other logging, if necessary.

     

                    // Trace to log file, event log, debugger output,

                    // depending on application configuration

                    Trace.WriteLine(exception);

                }

            }

    Here's an example of handling exceptions in a RunWorkerComplete event handler:

            private void backgroundWorker_RunWorkerCompleted ( object sender, RunWorkerCompletedEventArgs e )

            {

                try

                {

                    try

                    {

                        object theResult = e.Result;

                        // TODO: something with the result.

                    }

                    catch (System.Reflection.TargetInvocationException exception)

                    {

                        throw (exception.InnerException);

                    }

                }

                catch (System.Data.SqlClient.SqlException sqlException)

                {

                    System.Diagnostics.Trace.WriteLine("I knew what to do with SqlException and handled it.");

                }

            }

     

    Sunday, April 15, 2007 9:26 PM
    Moderator