views:

176

answers:

4

I don't do a lot of Windows GUI programming, so this may all be common knowledge to people more familiar with WinForms than I am. Unfortunately I have not been able to find any resources to explain the issue, I encountered today during debugging.

If we call EndInvoke on an async delegate. We will get any exception thrown during execution of the method re-thrown. The call stack will reflect the original source of the exception.

However, if we do something similar on a Windows.Forms.Control, the implementation of Control.EndInvoke resets the call stack. This can be observed by a simple test or by looking at the code in Reflector. The relevant code excerpt from EndInvoke is here:

if (entry.exception != null)
{
   throw entry.exception;
}

I understand that Begin/EndInvoke on Control and async delegates are different, but I would have expected similar behavior on Control.EndInvoke.

Is there any reason Control doesn't do whatever it is async delegates do to preserve the original call stack?

+1  A: 

I don't know the real reason but I can guess that async delegates are akin to RPC while control delegates may be based on Win32 Message sending. Different technologies so the impact of this feature may not be the same. Async delegate would benefit from all the remoting code, for which the developer would have written the code to transfer exception call stack between different process or computers, while control delegates will simulate RPC with PostMessage within the same process. Different team, different code.

plodoc
+1  A: 

Note also that Control.EndInvoke is one of the few Managed EndInvokes in the Framework (so you can see the code in Reflector). They probably ought to have a non-managed helper that throws with the original stack in place.

Mark Hurd
+1  A: 

I'm not sure why Control doesn't do this (probably just an oversight), but you can work around it in .NET 4.0 by scheduling a Task to the UI form:

    private BackgroundWorker bgw;
    private TaskFactory uiTaskFactory;

    private void Form1_Load(object sender, EventArgs e)
    {
        this.uiTaskFactory = new TaskFactory(TaskScheduler.FromCurrentSynchronizationContext());
        this.bgw = new BackgroundWorker();
        this.bgw.DoWork += bgw_DoWork;
        this.bgw.RunWorkerAsync();
    }

    void bgw_DoWork(object sender, DoWorkEventArgs e)
    {
        var task = this.uiTaskFactory.StartNew(this.OuterTaskFunction);
        try
        {
            task.Wait();
        }
        catch (Exception ex)
        {
            // Note: Full stack trace preserved
            MessageBox.Show(ex.InnerException.ToString());
        }
    }

    void OuterTaskFunction()
    {
        this.InnerTaskFunction();
    }

    void InnerTaskFunction()
    {
        throw new InvalidOperationException("Blah.");
    }
Stephen Cleary
A: 

I didn't read 100% of your message so I'm not sure if this helps or I'm just saying obvious things, but when an exception is caught and you write

"throw iAmAnCaughtExceptionInstance;"

the call stack will not be saved, you should just write

"throw;"

and then the call stack is saved

Blank6
I know, but since I am not the one who implemented Windows.Forms.Control it is of little help.
Brian Rasmussen