1

我使用 ContinueWith 运行一个任务,该任务具有双重职责:如果任务成功完成,则处理结果,或者如果确实发生错误,则处理任何异常。但是下面的代码不会正确处理任何异常,它会注册为未处理并关闭程序(为了发布而有所缩短,因此可能并不完美):

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,但这似乎并没有什么不同。谢谢你的帮助。

4

1 回答 1

2

问题是 fetchingTask<T>.ResultAggregateException 在那个点引发,这发生你真正抓住异常之前,并阻止你的方法被调用。

一种选择是使用两种延续 - 一种用于发生异常时,另一种用于未发生异常时:

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

于 2013-09-27T20:54:50.037 回答