views:

253

answers:

1

This might be a bit complicated, but bear with me.

I have a Windows Forms app. It uses strongly typed DataSets via the XSD designer. I am running data access queries via an asynchronous thread, performed like so:

// Calling it in code on the main thread:
LoadDataList_WorkerCaller dataDelegate = new LoadDataList_WorkerCaller(LoadDataList_Worker);
IAsyncResult iar = default(IAsyncResult);
iar = dataDelegate.BeginInvoke(LoadDataList_Complete, null);

// How they are defined in the class:
private delegate TypedDataSets.DataListDataTable LoadDataList_WorkerCaller();
private TypedDataSets.DataListDataTable LoadDataList_Worker()
{
    // ...blah blah
    try
    {
        DataListTableAdapter adapter = new DataListTableAdapter();
        TypedDataSets.DataListDataTable dataList = adapter.GetList(); // causes connection attempt
    }
    catch (SqlException sqlex)
    {
        // Log DB error and notify user of problem
    }

    return dataList;
}
private delegate void LoadDataList_CompleteCaller(IAsyncResult iar);
private void LoadDataList_Complete(IAsyncResult iar)
{
    if (InvokeRequired)
    {
        LoadDataList_CompleteCaller invokeDelegate = new LoadDataList_CompleteCaller(LoadDataList_Complete);
        Invoke(invokeDelegate, new object[] { iar });
        return;
    }

    // Downcast the IAsyncResult to an AsyncResult -- it's safe and provides extra methods
    System.Runtime.Remoting.Messaging.AsyncResult ar = (System.Runtime.Remoting.Messaging.AsyncResult)iar;

    LoadDataList_WorkerCaller dataDelegate = (LoadDataList_WorkerCaller)ar.AsyncDelegate;
    TypedDataSets.DataListDataTable dataList = null;

    try
    {
        dataList = dataDelegate.EndInvoke(iar);
    }
    catch (Exception ex)
    {
        // Final fail-safe, for non-DB exceptions; we'll log 'em and such here
    }

    // ... use dataList object as normal
}

I've based the multi-threading part on the suggestions in this SO question. It works fine... except when there are DB errors (in the form of SqlExceptions) when calling GetList() on the TableAdapter.

If I deliberately corrupt the connection string, which causes the connection attempts to fail, a series of about 10 SqlExceptions are generated somewhere in .NET framework code land and caught (first-chance exceptions). After that, the application throws an unhandled exception and terminates the program. I know this because I have a last-chance exception catcher on the Main method that logs them should something bubble up all the way. But my try-catch in the worker and callback functions, as detailed above, are never triggered. It seems like they are by-passed completely? Even though the line that starts the problems is inside a try-catch block.

It is an SqlException, so it should be caught, but instead the try-catch on the Main method catches a TargetInvocationException (with an SqlException as the InnerException). The error is as expected:

A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: TCP Provider, error: 0 - No connection could be made because the target machine actively refused it.)

But why is my try-catch missing it and I'm getting a TargetInvocationException instead? What gives? I get that this is because the exception happens on a separate thread from the main UI thread, but if the exception generated is generated in this separate thread in code that I have a try-catch with, why is it ignoring it and bailing on the thread entirely instead and then causing the main thread to panic and abort?

Ideally, the catch (SqlException) above should catch it, and I can notify via the UI as well as enable an Offline mode in case there are DB problems. The details don't matter for this question, suffice to say that I can handle it fully there and thus stop the exception in its tracks... if only my try-catch would actually catch it!

A: 

It appears to me that the problem lies somewhere outside of the code you've posted. If we assume that the dataset in question is using SqlClient, and the fully qualified type of the exception you're trying to catch is System.Data.SqlClient.SqlException, then the code should work exactly as you expect. One possible explanation for the problem is that the typed dataset is using System.Data.OleDb instead of System.Data.SqlClient.

Before you go any further, I recommend you add another catch block that catches an Exception instead of a SqlException. If you still can't catch the exception under those circumstances, then it must be that the structure of your code isn't what you expect and the exception is coming from somewhere else. By the way, it's a good idea to always wrap worker thread delegate code with a generic catch block to avoid the problem you're seeing.

Another good sanity check would be to fully qualify the type of the SqlException (System.Data SqlClient.SqlException). It's possible that in your code base somewhere someone has declared another class named SqlException that's hiding the real McCoy.

Paul Keister
It is certainly using SqlClient and all, and the exception type is correct (as I've confirmed via the InnerException of the TargetInvocationException). I've changed it to be a catch (Exception) and it does not behave any differently. I'll try placing more try-catches around until something finally catches. Thanks for at least giving it a consideration!
Yadyn
I suspect that the exception is not being thrown where you think it is. I'd suggest that you dump the full stack frame of the inner exception if you haven't done this already.
Paul Keister