I noticed a few strange behaviors in a Windows Forms scenario which involves threads and UI, so, naturally, this means making use of the InvokeRequired property. The situation: my application makes use of a thread to do some work and the thread sends an event into the UI. The UI displays a message based on an Internationalization system which consists of a dictionary with keys. The I18N system cannot find a key in the dictionary and crashes.
Notes: application is in Debug Mode and I have a try-catch over the entire “Application.Run();” back in Program.cs. However, that try-catch is not reached, as what I will discuss here is based on inner Exception handling, but I mentioned it just in case.
So now here comes the fun parts:
-
Why, for the life of me, does Visual Studio “censor” exception information from me? In the code below, you will see on the if (InvokeRequired) branch, a try-catch. I log the exception. ex.InnerException is NULL and the provided ex.StackTrace is anemic (only 1 step in it). Now if I comment the try-catch and simply let it crash via the Debugger, I get a much ampler stack trace. Why is that?
-
To make things worse, neither of the two stack traces versions contain any information about the i18N crash. They just say “The given key was not present in the dictionary.” and give me a stack trace up to the Invoke declaration.
-
On the else branch (that is, InvokeRequired == false), if I put a try-catch, I can successfully catch my Exception back to the i18n system. As you can see, I tried to send my exception with InnerException back to the InvokeRequired == true branch. However, even so, InnerException stays NULL there and I cannot access my i18N error.
I am puzzled by all these things and maybe somebody can help shed some light over here. If you got really strong lanterns that is.
Here is the function’s code.
private delegate void AddMessageToConsole_DELEGATE (frmMainPresenter.PresenterMessages message);
private void AddMessageToConsole (frmMainPresenter.PresenterMessages message)
{
if (InvokeRequired)
{ //Catching any errors that occur inside the invoked function.
try { Invoke(new AddMessageToConsole_DELEGATE(AddMessageToConsole), message); }
catch (Exception ex) { MSASession.ErrorLogger.Log(ex); }
//Invoke(new AddMessageToConsole_DELEGATE(AddMessageToConsole), message);
}
else
{
string message_text = ""; //Message that will be displayed in the Console / written in the Log.
try
{
message_text = I18N.GetTranslatedText(message)
}
catch (Exception ex)
{
throw new Exception(ex.Message, ex);
}
txtConsole.AppendText(message_text);
}
}
The call stack problem is a known issue with
Control.Invoke. You lose the call stack. Sorry. This is because it is rethrown on the UI thread usingthrow ex;.The best solution would be to replace the background thread with a background
Task. Note: this solution is only available for .NET 4.0. TheTaskclass properly marshals exceptions. I wrote a blog entry about reporting progress from tasks, and the code in that blog entry will allow you to catch any UI update errors in the background thread, preserving the original exception and its call stack.If you can’t upgrade to .NET 4.0 yet, there is a workaround. Microsoft’s Rx library includes a CoreEx.dll which has an extension method for
ExceptioncalledPrepareForRethrow. This is supported in .NET 3.5 SP1 and .NET 4.0 (and SL 3 and SL 4). You’ll need to wrap your UI updater method with something a little uglier:Note: I recommend you start a migration away from
ISynchronizeInvoke. It is an outdated interface that is not carried forward into newer UI frameworks (e.g., WPF, Silverlight). The replacement isSynchronizationContext, which supports WinForms, WPF, Silverlight, ASP.NET, etc.SynchronizationContextis much more suitable as an abstract “thread context” for a business layer.