使用Await在可枚举对象上

5

我有一块代码,想要对commands可枚举对象中的每个命令都应用using语句。请问C#中的语法是什么?

await using var transaction = await conn.BeginTransactionAsync(cancel);
IEnumerable<DbCommand> commands = BuildSnowflakeCommands(conn, tenantId);

var commandTasks = new List<Task>();
foreach (var command in commands)
{
    command.CommandTimeout = commandTimeout;
    command.Transaction = transaction;
    commandTasks.Add(command.ExecuteNonQueryAsync(cancel));
}

try
{
    await Task.WhenAll(commandTasks);
}
catch (SnowflakeDbException)
{
    await transaction.RollbackAsync(cancel);
    return;
}

await transaction.CommitAsync(cancel);

编辑:Rider/ReSharper似乎认为这两个语句是等价的,或者至少我会得到一个提示将for转换为foreach(这显然是错误的):

for (var i = 0; i < commands.Count; i++)
{
    await using var command = commands[i];
    command.CommandTimeout = commandTimeout;
    command.Transaction = transaction;
    commandTasks.Add(command.ExecuteNonQueryAsync(cancel));
}

并且

foreach (var command in commands)
{
    command.CommandTimeout = commandTimeout;
    command.Transaction = transaction;
    commandTasks.Add(command.ExecuteNonQueryAsync(cancel));
}

编辑2:经过一些讨论和一些有用的回答,这是我要采用的方案:

var transaction = await conn.BeginTransactionAsync(cancel);
var commands = BuildSnowflakeCommands(conn, tenantId);

var commandTasks = commands.Select(async command =>
{
    await using (command)
    {
        command.CommandTimeout = commandTimeout;
        command.Transaction = transaction;
        await command.ExecuteNonQueryAsync(cancel);
    }
});

try
{
    await Task.WhenAll(commandTasks);
    await transaction.CommitAsync(cancel);
}
catch (SnowflakeDbException)
{
    await transaction.RollbackAsync(cancel);
}
finally
{
    await transaction.DisposeAsync();
}
2个回答

5

您可以使用LINQ:

var commandTasks = commands.Select(async command =>
{
    using (command)
    {
        command.CommandTimeout = commandTimeout;
        command.Transaction = transaction;
        await command.ExecuteNonQueryAsync(cancel);
    }
});

当命令执行结束后,它将被立即释放。

完整代码:

await using var transaction = await conn.BeginTransactionAsync(cancel);
IEnumerable<DbCommand> commands = BuildSnowflakeCommands(conn, tenantId);

var commandTasks = commands.Select(async command =>
{
    using (command)
    {
        command.CommandTimeout = commandTimeout;
        command.Transaction = transaction;
        await command.ExecuteNonQueryAsync(cancel);
    }
});

try
{
    await Task.WhenAll(commandTasks);
}
catch (SnowflakeDbException)
{
    await transaction.RollbackAsync(cancel);
    return;
}

await transaction.CommitAsync(cancel);

千万不要使用 for 循环的示例;await 会导致每个命令都按顺序执行,因为必须在启动下一个查询之前等待每个查询的完成。


2
@xandermonkey 不,当 await 被触发时,async lambda 将立即返回一个 Task,允许每个命令并行执行。每个命令只有在 ExecuteNonQueryAsync 完成后才会被处理。 - Johnathan Barclay
好的。不过还有一个问题。捕获的变量 transaction 现在在外部作用域中被释放了。 - xandermonkey
抱歉使用imgur链接,这是展示警告悬停文本的简单方法:https://imgur.com/a/rHaTJTc - xandermonkey
1
@xandermonkey 你尝试运行代码了吗?虽然事务在外部范围中被处理,但它不会离开范围,直到所有命令完成。 - Johnathan Barclay
@JohnathanBarclay 完全正确,它确实有效。我正在编辑我的答案以展示完整的解决方案 - 我只是手动处理事务以确保安全。 - xandermonkey
显示剩余6条评论

1

你无法仅使用语言语法调用一组 IDisposableIAsyncDisposableDisposeDisposeAsync 方法。

你可以迭代每一个并调用相应的方法。在重新枚举之前,我建议将所有命令缓存为数组或只读集合。

我建议避免在循环中进行处理,正如 JetBrains 工具推荐的那样。该命令必须存在更长时间。

个人而言,我会这样做:

var commands = BuildSnowflakeCommands(conn, tenantId).ToArray();
var commandTasks = new List<Task>(commands.Length);

foreach (var command in commands)
{
    command.CommandTimeout = commandTimeout;
    command.Transaction = transaction;
    commandTasks.Add(command.ExecuteNonQueryAsync(cancel));
}

/// later...
foreach (var command in commands)
{
    command.Dispose();
}

有趣。请看我的编辑过的帖子,我很想进一步了解这个问题。 - xandermonkey
command.Dispose(); 这种写法容易出错,因为对象可能已经被处理过了。command?.Dispose(); 更加健壮一些。 - Peter Csala
4
@PeterCsala,空值传播运算符与可处理对象没有关系。 - Daniel A. White
@JohnathanBarclay 当然可以。但这并没有在帖子里提到。 - Daniel A. White
@DanielA.White 是的,没错。 - Johnathan Barclay
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接