Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Await using on enumerable

I have a block of code where I want to apply the using statement to each command in the commands enumerable. What is the C# syntax for this?

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);

Edit: Rider / ReSharper seems to think these two are equivalent, or at least I get a prompt to convert the for into a foreach (this is clearly wrong):

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));
}

and

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

Edit 2: After some discussion and a few helpful answers, this is what I'm going with:

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();
}
like image 477
xandermonkey Avatar asked Jun 03 '20 14:06

xandermonkey


People also ask

What is IAsyncEnumerable in C#?

IAsyncEnumerable<T> exposes an enumerator that has a MoveNextAsync() method that can be awaited. This means a method that produces this result can make asynchronous calls in between yielding results.

What is task IEnumerable?

WhenAll(IEnumerable<Task>) Creates a task that will complete when all of the Task objects in an enumerable collection have completed. WhenAll(Task[]) Creates a task that will complete when all of the Task objects in an array have completed.

How does await foreach work?

This method is used when you have a bunch of tasks that you want to await all at once. We then await the method and wait for all the Tasks in our collection to complete. Once done, the method returns its Task as completed to its caller and our logic is complete.

Does async await improve performance?

C# Language Async-Await Async/await will only improve performance if it allows the machine to do additional work.


2 Answers

You can use LINQ:

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

The command will then be disposed as soon as it exits scope.

Full code:

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);

Definitely don't use the for loop example; the await will cause each command to happen in serial, as the completion of each query must be awaited before the next is initiated.

like image 66
Johnathan Barclay Avatar answered Oct 17 '22 13:10

Johnathan Barclay


You can't call Dispose or DisposeAsync a set of IDisposables or IAsyncDisposables using the language syntax alone.

You can iterate each one and call the appropriate method. I would cache all the commands as an array or readonly collection before re-enumerating.

I would avoid disposing in your loop as your JetBrains tooling recommends. The command has to live longer than just that.

Personally, I would do something like this:

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();
}
like image 31
Daniel A. White Avatar answered Oct 17 '22 12:10

Daniel A. White