Task.WaitAll,获取所有异常和单个任务持续时间

时间:2018-06-04 13:11:33

标签: c# asynchronous exception-handling stopwatch

我正在开发一个实用程序,它将使用ExecuteNonQueryAsync并行执行多个SqlCommands。该实用程序只应在所有请求完成后返回,因此我使用的是WaitAll。

实际上,此实用程序是SQL CLR存储过程。 这排除了使用Task.Run()

我想捕获所有发生的个别异常,这似乎工作正常。我现在正在尝试添加能够获得数组中每个单独任务的已用时间的能力。

以下是代码的核心,没有任何已用时间信息:

// the commands array has been populated with an array of strings (TSQL statemements to execute)
Task<int>[] tasks = new Task<int>[commands.Length];

try {
    for (int i = 0; i < commands.Length; i++) {
        // executeSQL is an asyc method which makes the appropriate connection and returns await ExecuteNonQueryAsync
        tasks[i] = executeSql(commands[i], connectionString, (int)commandTimeout);
    }
    Task.WaitAll(tasks);
} catch (AggregateException) {
    // exceptions reported in GetResults
} finally { 
    //GetResults builds a datatable of { command, result, exception text }
    var results = GetResults(commands, tasks);
}

现在,我正在努力弄清楚如何获取每个ExecuteNonQueryAsync请求的已用时间。

我的第一个想法是创建一个秒表数组,并在executeSQL调用中添加一个秒表参数,然后将watches数组传递给GetResults函数:

Task<int>[] tasks = new Task<int>[commands.Length];
Stopwatch[] watches = new Stopwatch[commands.Length];

try {
    for (int i = 0; i < commands.Length; i++) {
        tasks[i] = executeSql(commands[i], connectionString, (int)commandTimeout, watches[i]);
    }
    Task.WaitAll(tasks);
} catch (AggregateException) {
    // exceptions reported in finally
} finally {
    var results = GetResults(commands, tasks, watches);
}

我可以在executeSQL函数中调用ExecuteNonQueryAsync之前启动秒表。但我什么时候可以阻止它?

这是我试图解决问题的方法。我想我会添加一个延续来停止秒表,并等待继续。但是这段代码无效(无法隐式转换类型&#39; System.Runtime.CompilerServices.ConfiguredTaskAwaitable&gt;&#39; System.Threading.Tasks.Task&#39;

var tasks = new Task<int>[commands.Length];
var watches = new System.Diagnostics.Stopwatch[commands.Length];

try {
    for (int i = 0; i < commands.Length; i++) {
        tasks[i] = executeSql(commands[i], connectionString, (int)commandTimeout, watches[i]).ContinueWith(
            t => {
                watches[i].Stop();
                return t;
            },
            TaskContinuationOptions.AttachedToParent
        ).ConfigureAwait(false);
    }
    Task.WaitAll(tasks);
} catch (AggregateException) {
    // ...

1 个答案:

答案 0 :(得分:0)

嗯,答案是&#34;简单&#34;解决方案实际上确实有效,尽管我预计它不会。

下面是我对executeSQL函数的新定义,以及它是如何被调用的。

让我的大脑受伤的是异常处理。我想如果语言本身将我的结果转化为任务,并在需要时神奇地将异常附加到任务中的那些结果,那么,肯定,如果我只是以正常的方式抛出异常(如根据下面的最后一个块),会发生意想不到的事情(事实上​​,ExecuteNonQueryAsync返回一个Task&lt; int&gt;,但是我必须将结果分配给一个int - 而不是一个Task&lt; int&gt; - 对我来说仍然很奇怪,我喜欢它。)

但是不!它似乎工作。

static async internal Task<int> executeSql(string tsql, string connectionString, int commandTimeout, System.Diagnostics.Stopwatch watch) {
    int i = -1;
    Exception ex = null;
    try {
        using (var con = new SqlConnection(connectionString)) {
            con.Open();
            using (var cmd = new SqlCommand("set xact_abort on;" + tsql, con)) {
                cmd.CommandType = System.Data.CommandType.Text;
                cmd.CommandTimeout = commandTimeout;
                watch.Start();
                i = await cmd.ExecuteNonQueryAsync().ConfigureAwait(false);
            }
        }
    } catch (Exception e) {
        ex = e;
    } finally {
        watch.Stop();
        if (ex != null) { throw ex; }
    }
    return i;
}

// ....

var tasks = new Task<int>[commands.Length];
var watches = new System.Diagnostics.Stopwatch[commands.Length];

try {
    for (int i = 0; i < commands.Length; i++) {
        watches[i] = new System.Diagnostics.Stopwatch();
        tasks[i] = executeSql(commands[i], connectionString, (int)commandTimeout, watches[i]);
    }
    Task.WaitAll(tasks);
} catch (AggregateException) { 
    // ...