Thursday, October 8, 2015

WPF Application.Current.FindResource() and uncaughtable exceptions

One more time to remind about The Old New Thing blog and trend to blame yourself first instead of Windows.
Today it was a trivial day with trivial task: find a resource by its name at a WPF app. What can be a problem, really? The plan was simple:
  1. Call Application.Current.FindResource()
  2. Wrap it into try...catch block as MSDN says we can meet ResourceReferenceKeyNotFoundException if the resource is absent or somehow missed in the app package.
Well, let's code!
try
{
    var x = Application.Current.FindResource("AnyLongStringYouDontExpectToFind");
}
catch (ResourceReferenceKeyNotFoundException)
{
    // any logic to resolve the error
}
Stright-forward non-surprizing code, right? So, you can understand how awazed I was once I didn't catch the exception and got the app closed instead. Quickly I placed a breakpoint into the "catch" body. Exception is not handled, breakpoint is not being hit! Well, let expand the catch filter from ResourceReferenceKeyNotFoundException to general Exception. Exception is not handled, breakpoint is not being hit, again! What's happening? .NET bug?
Trying to investigate the issue I ran that code several times. And one time occasionally I got the breakpoint being hit right after the exception that pretended being unhandled, and before the app is terminated. First shed of light? Exactly!
Let take a look at our slightly modified code. We're about to add this at the app init part:
    Application.Current.Dispatcher.UnhandledException += Dispatcher_Hook;
...
    void Dispatcher_Hook(object sender, DispatcherUnhandledExceptionEventArgs e)
    {
        // make a log record, cleanup the staff etc. and die
        Application.Current.Shutdown();
    }
...
Then, put a breakpoint into Dispatcher_Hook() and run our old code:
try
{
    var x = Application.Current.FindResource("AnyLongStringYouDontExpectToFind");
}
catch (ResourceReferenceKeyNotFoundException)
{
    // any logic to resolve the error
}
So, once we got ResourceReferenceKeyNotFoundException raised, it's jumped into Dispatcher_Hook() and asked the app to close, then it twisted back into our catch block. Why?

We're innocent, aren't we?
Indeed, there is something wrong with FindResource() itself. It seems it uses another thread or at least different dispatching context to iterate over the resources. You can see "dead thread" in the Debug->Windows->Threads as an artifact. So, .NET authors just forgor to wrap the exception and pass it correctly to the calling thread. To prove this point, you can try the following amend: we'll raise the exception by ourselves!
try
{
    throw new ResourceReferenceKeyNotFoundException("We're innocent!", null);
    //var x = Application.Current.FindResource("AnyLongStringYouDontExpectToFind");
}
catch (ResourceReferenceKeyNotFoundException)
{
    // any logic to resolve the error
}
Needless to say we catch the exception normal way now, no unhandled exceptions triggered.

The only hope
So, FindResource() don't come into the trouble just because most of apps do not handle unhandled exceptions on the global scope. That pity worker thread just raises the exception and dies (maybe, with minor memory/resource leak). As long as nobody cares, that thread death has no app-level impact and used to be unnoticed.
Apparently, it seems we can't use FindResource() and unhandled exceptions trap at the same time. What can we do? MSDN comes to rescue:

If you call this method for a key that cannot be found, an exception is thrown. If you do not want to handle exceptions that result from calling FindResource, call TryFindResource instead; TryFindResource returns a null reference when a requested resource cannot be found, and does not throw an exception.

Nothing more to add: use TryFindResource() instead and check the result for null.

No comments:

Post a Comment