我应该如何从.NET 4.0中的延续任务传播任务异常?

时间:2013-06-08 00:37:14

标签: c# .net asynchronous task-parallel-library

我有以下代码:

innerExceptions = dbconnByServer
    .AsParallel()
    .WithDegreeOfParallelism(dbconnByServer.Count)
    // A stream of groups of server connections proceeding in parallel per server
    .Select(dbconns => dbconns.Select(dbconn => m_sqlUtilProvider.Get(dbconn)))
    // A stream of groups of SqlUtil objects proceeding in parallel per server
    .Select(sqlUtils => GetTaskException(sqlUtils
        // Aggregate SqlUtil objects to form a single Task which runs the SQL asynchronously for the first SqlUtil, then upon completion
        // for the next SqlUtil and so long until all the SqlUtil objects are processed asynchronously one after another.
        .Aggregate<ISqlUtil, Task>(null, (res, sqlUtil) =>
        {
            if (res == null)
            {
                return sqlUtil.ExecuteSqlAsync(SQL, parameters);
            }
            return res.ContinueWith(_ => sqlUtil.ExecuteSqlAsync(SQL, parameters)).Unwrap();
        })))
    .Where(e => e != null)
    .ToList();

其中:

private static Exception GetTaskException(Task t)
{
    try
    {
        t.Wait();
        return null;
    }
    catch (Exception exc)
    {
        return exc;
    }
}

此代码执行的操作是跨多个数据库连接执行某些SQL语句,其中某些连接可能属于一个数据库服务器,而其他连接可能属于另一个连接,依此类推。

代码确保有两个条件:

  1. SQL语句在可用的数据库服务器上并行运行。
  2. 在同一个数据库服务器中,SQL语句是异步运行的,但是按顺序运行。
  3. 给定每个数据库服务器的N db连接,在聚合结束时会有一个Task,执行会产生以下影响:

    • 执行db conn 1的SQL
      • 完成上一个后,执行db conn 2的SQL
        • 完成上一个后,执行db for conn 3
        • 的SQL
        • ...
          • 完成上一个后,执行db for conn N
          • 的SQL

    我的问题是,除了第一个数据库连接之外,现在除了异常。我知道我应该检查_参数并以某种方式处理continuation函数中的_.Exception属性。我想知道是否有一种优雅的方式来做到这一点。

    有什么想法吗?

1 个答案:

答案 0 :(得分:1)

  1. 如果你打算使用异步方法,那么整个方法应该是异步的,不应该阻塞任何线程。
  2. 如果您不打算阻止,没有太多理由使用PLINQ,从单个线程设置延续应该足够快。
  3. 如果您想在发生异常时继续,则必须自己将异常存储在某处。
  4. 我对使用异常集合而不抛弃它感到有点不安,但我想这对于一个可能部分失败并部分成功的操作是可以接受的。
  5. 有了这个,我的代码就像这样:

    public Task<IEnumerable<Exception>> ExecuteOnServersAsync(
        IList<IEnumerable<Connection>> dbConnByServer,
        string sql, object parameters)
    {
        var tasks = new List<Task>();
        var exceptions = new ConcurrentQueue<Exception>();
    
        Action<Task> handleException = t =>
        {
            if (t.IsFaulted)
                exceptions.Enqueue(t.Exception);
        };
    
        foreach (var dbConns in dbConnByServer)
        {
            Task task = null;
    
            foreach (var dbConn in dbConns)
            {
                var sqlUtil = m_sqlUtilProvider.Get(dbConn);
    
                if (task == null)
                {
                    task = sqlUtil.ExecuteSqlAsync(sql, parameters);
                }
                else
                {
                    task = task.ContinueWith(
                        t =>
                        {
                            handleException(t);
                            return sqlUtil.ExecuteSqlAsync(sql, parameters);
                        }).Unwrap();
                }
            }
    
            if (task != null)
            {
                task = task.ContinueWith(handleException);
                tasks.Add(task);
            }
        }
    
        return Task.Factory.ContinueWhenAll(
            tasks.ToArray(), _ => exceptions.AsEnumerable());
    }