我运行一个带有双重任务的ContinueWith的任务:如果任务成功完成,则处理结果;如果发生错误,则处理任何异常。但是下面的代码不能正确处理任何异常,它会注册为unhandeled并关闭程序(稍微缩短发布时间,因此可能不完美):
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;
}
我正在使用TaskContinuationOptions.None将其关闭,我认为这是正确的。这在上面的类的基类中声明继承自:
protected Task _TaskLoader;
protected CancellationTokenSource _TaskCanceller;
如果我在一个不会导致错误的场景下运行,一切都很顺利,我得到了我的数据库列表。但是,如果出现错误,请说有人为此SQL Server登录凭据提供了错误密码,则错误不会被处理。
但是如果我删除了传递Result参数的选项,那么一切正常,并且会捕获异常:
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;
}
我认为我并不完全理解TPL是如何工作的。我尝试创建一个以上的ContinueWith,这似乎没有什么区别。谢谢你的帮助。
答案 0 :(得分:2)
问题是,抓取Task<T>.Result
会在此时引发AggregateException
,在之前发生,您实际上可以获取异常,并且阻止你的方法被调用。
一种选择是使用两个延续 - 一个用于发生异常时,一个用于何时不发生:
_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());
另一种选择是将Task<T>
本身(antecendant
)作为参数传递给方法。然后,您可以检查task.Exception
,如果它不为空,则显示异常,否则,处理结果。