Reputation: 14270
I running a task with a ContinueWith that pulls double duty: Processes the results if the task completes successfully or an handles any exception if an error does happen. But the below code will not handle any exception properly and it registers as unhandeled and brings down the program (shortened somewhat for posting so may not be perfect):
void _SqlServerDatabaseListLoader()
{
_ClearSqlHolders(true, false);
_SqlConnectionStringHolder.Database = "master";
if (_SqlConnectionStringHolder.IsComplete)
{
//Could time out put on its own thread with a continuation back on the UI thread for the popup
_TaskCanceller = new CancellationTokenSource();
_TaskLoader = Task.Factory.StartNew(() =>
{
IsLoadingSqlServerDatabaseList = true;
using (SqlConnection con = new SqlConnection(_SqlConnectionStringHolder))
{
// Open connection
con.Open(); //If this cause an error (say bad password) the whole thing bombs
//create a linq connection and get the list of database names
DataContext dc = new DataContext(con);
return new ObservableCollection<string>(dc.ExecuteQuery<string>("select [name] from sys.databases").ToObservableCollection());
}
}).ContinueWith(antecendant => _SqlServerDatabaseListLoaderComplete(antecendant.Result, antecendant.Exception),
_TaskCanceller.Token,
TaskContinuationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext());
}
}
void _SqlServerDatabaseListLoaderComplete(ObservableCollection<string> DatabaseList, AggregateException ae)
{
//Just show the first error
if (ae != null)
ToolkitDialog.ShowException(ae.InnerExceptions[0], ToolkitDialogType.Error, CustomDialogButtons.OK, "Error:", "Database List Error");
if(DatabaseList != null)
SqlServerDatabaseList = DatabaseList
//Set the running indicator
_TaskLoader = null;
_TaskCanceller = null;
IsLoadingSqlServerDatabaseList = false;
}
I am using the TaskContinuationOptions.None to pull this off, I assume that is correct. This declared in the base class this above class inherits from:
protected Task _TaskLoader;
protected CancellationTokenSource _TaskCanceller;
If I run under a scenario that does not result in error, everything goes fine and I get my Database Listing. But if there is an error, say someone gives a bad password for this SQL Server login credentials, the error is not handeled.
But if I removed the option to pass the Result parameter, everything works as it should and exceptions are caught:
void _SqlServerDatabaseListLoader()
{
_ClearSqlHolders(true, false);
_SqlConnectionStringHolder.Database = "master";
if (_SqlConnectionStringHolder.IsComplete)
{
//Could time out put on its own thread with a continuation back on the UI thread for the popup
_TaskCanceller = new CancellationTokenSource();
_TaskLoader = Task.Factory.StartNew(() =>
{
IsLoadingSqlServerDatabaseList = true;
using (SqlConnection con = new SqlConnection(_SqlConnectionStringHolder))
{
// Open connection
con.Open();
//create a linq connection and get the list of database names
DataContext dc = new DataContext(con);
//HAVE TO SET IN THE THEAD AND NOT RETURN A RESULT
SqlServerDatabaseList = new ObservableCollection<string>(dc.ExecuteQuery<string>("select [name] from sys.databases").ToObservableCollection());
}
}).ContinueWith(antecendant => _SqlServerDatabaseListLoaderComplete(antecendant.Exception),
_TaskCanceller.Token,
TaskContinuationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext());
}
}
void _SqlServerDatabaseListLoaderComplete(AggregateException ae)
{
//Just show the first error
if (ae != null)
ToolkitDialog.ShowException(ae.InnerExceptions[0], ToolkitDialogType.Error, CustomDialogButtons.OK, "Error:", "Database List Error");
//Set the running indicator
_TaskLoader = null;
_TaskCanceller = null;
IsLoadingSqlServerDatabaseList = false;
}
I assume I am not fully understanding how TPL is suppose to work. I tried creating more then one ContinueWith and that did not seem to make a difference. Thanks for any help.
Upvotes: 1
Views: 812
Reputation: 564641
The problem is that fetching Task<T>.Result
will raise the AggregateException
at that point, which occurs before you can actually grab the exception, and prevents your method from being called.
One option is to use two continuations - one for when an exception occurs, and one for when it doesn't:
_TaskLoader = Task.Factory.StartNew(() =>
{
IsLoadingSqlServerDatabaseList = true;
using (SqlConnection con = new SqlConnection(_SqlConnectionStringHolder))
{
// Open connection
con.Open();
//create a linq connection and get the list of database names
DataContext dc = new DataContext(con);
//HAVE TO SET IN THE THEAD AND NOT RETURN A RESULT
SqlServerDatabaseList = new ObservableCollection<string>(dc.ExecuteQuery<string>("select [name] from sys.databases").ToObservableCollection());
}
});
// This method is called if you get an exception, and processes it
_TaskLoader.ContinueWith(antecendant => _SqlServerDatabaseListLoaderFaulted(antecendant.Exception),
_TaskCanceller.Token,
TaskContinuationOptions.OnlyOnFaulted,
TaskScheduler.FromCurrentSynchronizationContext());
// This method is called if you don't get an exception, and can safely use the result
_TaskLoader.ContinueWith(antecendant => _SqlServerDatabaseListLoaderCompleted(antecendant.Result),
_TaskCanceller.Token,
TaskContinuationOptions.NotOnFaulted,
TaskScheduler.FromCurrentSynchronizationContext());
The other option would be to pass the Task<T>
itself (antecendant
) as the argument to the method. You can then check for task.Exception
, and if it's not null, show the exception, otherwise, process the results.
Upvotes: 2