TPL任务ContinueWith结果和异常导致未处理的异常

时间:2013-09-27 20:48:59

标签: .net wpf exception task-parallel-library

我运行一个带有双重任务的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,这似乎没有什么区别。谢谢你的帮助。

1 个答案:

答案 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,如果它不为空,则显示异常,否则,处理结果。