Within Run
, I'm trying to queue 10,000 tasks. (This is just an experiment so I can better understand async await
.) But the first loop takes over three seconds to complete. I would expect this to be faster since I'm just trying to queue the tasks and await
them a few lines later. Still within Run
, alreadyCompleted
is true because the tasks seem to have run synchronously. Finally, still within Run
, the second loop takes 1 millisecond to complete, again showing the tasks have already run.
How can I queue tasks to run while still allowing execution to progress through the Run
method until I use await
? I would understand if some of my tasks had completed by the time alreadyCompleted
gets checked, but it seems weird that all of them have completed. This behavior is consistent between ASP.NET Core 3, .NET Core 3 Console, and .NET Framework Console. Thank you.
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
public static class SqlUtility
{
public static async Task Run()
{
const int max = 10000;
Stopwatch sw1 = Stopwatch.StartNew();
Task<int>[] tasks = new Task<int>[max];
for(int i=0;i<max;i++)
{
tasks[i] = GetVideoID();
}
sw1.Stop();//ElapsedMilliseconds: 3169
bool alreadyCompleted = tasks.All(t => t.IsCompletedSuccessfully);//true
Stopwatch sw2 = Stopwatch.StartNew();
for (int i = 0; i < max; i++)
{
await tasks[i].ConfigureAwait(false);
}
sw2.Stop();//ElapsedMilliseconds: 1
}
public static async Task<int> GetVideoID()
{
const string connectionString =
"Server=localhost;Database=[Redacted];Integrated Security=true";
SqlParameter[] parameters = new SqlParameter[]
{
new SqlParameter("VideoID", SqlDbType.Int) { Value = 1000 }
};
const string commandText = "select * from Video where VideoID=@VideoID";
IAsyncEnumerable<object> values = GetValuesAsync(connectionString, parameters, commandText,
CancellationToken.None);
object videoID = await values.FirstAsync().ConfigureAwait(false);//1000
return (int)videoID;
}
public static async IAsyncEnumerable<object> GetValuesAsync(
string connectionString, SqlParameter[] parameters, string commandText,
[EnumeratorCancellation]CancellationToken cancellationToken)
{
using (SqlConnection connection = new SqlConnection(connectionString))
{
await connection.OpenAsync(cancellationToken).ConfigureAwait(false);
using (SqlCommand command = new SqlCommand(commandText, connection))
{
command.Parameters.AddRange(parameters);
using (var reader = await command.ExecuteReaderAsync()
.ConfigureAwait(false))
{
while (await reader.ReadAsync().ConfigureAwait(false))
{
yield return reader[0];
}
}
}
}
}
}
Update
While running a .NET Core application and using a max
of 1,000, alreadyCompleted
is false. In fact, looking at IsCompleted
of each task, none of the tasks are completed where alreadyCompleted
is currently. This is what I would have expected to happen in my original scenario.
But if max
is set to 10,000 (as it was originally), alreadyCompleted
is false, just as above. Interestingly, checking tasks[max - 1].IsCompleted
results in false
(checking the last task first). tasks.All(t => t.IsCompletedSuccessfully)
checks the last result last, at which point IsCompleted
is true
.