首页 > 解决方案 > Task.WaitAll,获取所有异常和单个任务持续时间

问题描述

我正在开发一个实用程序,它将使用 ExecuteNonQueryAsync 并行执行多个 SqlCommand。该实用程序仅应在所有请求完成后返回,因此我使用的是 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 调用中添加一个秒表参数,然后将手表数组传递给 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 之前启动秒表。但是我什么时候可以阻止它?

这就是我试图解决问题的方式。我想我会添加一个延续来停止秒表,然后等待延续。但是此代码无效(无法将类型 'System.Runtime.CompilerServices.ConfiguredTaskAwaitable>' 隐式转换为 'System.Threading.Tasks.Task'

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) {
    // ...

标签: c#asynchronousexception-handlingstopwatch

解决方案


好吧,答案是“简单”的解决方案确实有效,尽管我预计它不会。

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

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

但是不行!它似乎工作。

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) { 
    // ...

推荐阅读