Skip to content

Instantly share code, notes, and snippets.

@cheenamalhotra
Last active May 10, 2021 22:10
Show Gist options
  • Save cheenamalhotra/fcc840913562c534c5905aaea553e5a1 to your computer and use it in GitHub Desktop.
Save cheenamalhotra/fcc840913562c534c5905aaea553e5a1 to your computer and use it in GitHub Desktop.
Reproduces slow performance of ReadAsync when reading a large row.
using System;
using System.Data;
using Microsoft.Data.SqlClient;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace ReproApps
{
class ReadAsyncTest
{
const int TimeoutInSeconds = 5;
static string s_connectionString = $"Server=localhost;Initial Catalog=master;Integrated Security=true;";
static readonly string s_tableName = "SOMELARGETABLE";
static readonly string s_tableCreateQuery = $"CREATE TABLE {s_tableName}(c1 nvarchar(max));";
static readonly string s_tableInsertParamQuery = $"INSERT {s_tableName} values (@)";
static readonly string s_tableUpdateParamQuery = $"UPDATE {s_tableName} SET c1 = c1 + @ ";
static readonly string s_tableSelectQuery = $"SELECT * FROM {s_tableName};";
static readonly string s_tableDropQuery = $"DROP TABLE IF EXISTS {s_tableName}";
static readonly CancellationTokenSource s_tokenSource = new();
static Stopwatch s_stopwatch;
public static void RunTest(string connString = null)
{
Console.WriteLine($"Application started. Timeout in seconds: {TimeoutInSeconds}");
s_stopwatch = Stopwatch.StartNew();
if (connString != null)
{
s_connectionString = connString;
}
using SqlConnection con = new(s_connectionString);
con.Open();
try
{
DropTable(con);
CreateTable(con);
GenerateAndInsertLargeDataRow(con);
s_tokenSource.CancelAfter(TimeoutInSeconds * 1000);
Task.WaitAll(ReadLargeFile(), TimeStat());
}
catch (AggregateException ex)
{
foreach (Exception e in ex.InnerExceptions)
{
Console.WriteLine(e);
}
}
finally
{
s_tokenSource.Dispose();
DropTable(con);
s_stopwatch.Stop();
Console.WriteLine($"Total Elapsed time: --------------- {s_stopwatch.Elapsed}\nApplication ending.");
}
}
static async Task ReadLargeFile()
{
using SqlConnection con = new(s_connectionString);
await con.OpenAsync(s_tokenSource.Token).ConfigureAwait(false);
using SqlCommand command = new(s_tableSelectQuery, con);
command.CommandTimeout = TimeoutInSeconds;
SqlDataReader reader = await command.ExecuteReaderAsync(CommandBehavior.Default, s_tokenSource.Token).ConfigureAwait(false);
DataTable schemaTable = reader.GetSchemaTable();
// while(reader.Read()) // Problem 1: Performance very slow for ReadAsync() when compared to Read()
while (await reader.ReadAsync(s_tokenSource.Token).ConfigureAwait(false)) // Problem 2: Cancellation does not stop ReadAsync from continuing to Read.
{
Console.WriteLine($"Finished Read iteration. Checking cancellation >");
s_tokenSource.Token.ThrowIfCancellationRequested();
}
}
static async Task TimeStat(int secondsDelay = 10)
{
try
{
while (!s_tokenSource.IsCancellationRequested)
{
Console.WriteLine($"#TotalSeconds: {s_stopwatch.Elapsed.TotalSeconds}");
await Task.Delay(new TimeSpan(0, 0, secondsDelay), s_tokenSource.Token).ConfigureAwait(false);
}
}
catch (TaskCanceledException)
{
Console.WriteLine("Cancellation has been requested. Finishing TimeStat task.");
}
}
private static void CreateTable(SqlConnection con)
{
using (SqlCommand command = new(s_tableCreateQuery, con))
{
command.ExecuteNonQuery();
}
Console.WriteLine("Table created");
}
private static void DropTable(SqlConnection con)
{
using (SqlCommand command = new(s_tableDropQuery, con))
{
command.ExecuteNonQuery();
}
Console.WriteLine("Table dropped");
}
private static void GenerateAndInsertLargeDataRow(SqlConnection con)
{
string large = new('a', 512 * 1024);
using (var command = new SqlCommand(s_tableInsertParamQuery, con))
{
command.Parameters.AddWithValue("@", large);
command.ExecuteNonQuery();
}
// Increase this counter to make a larger string.
for (int i = 0; i < 15; i++)
{
using SqlCommand command = new(s_tableUpdateParamQuery, con);
command.Parameters.AddWithValue("@", large);
command.ExecuteNonQuery();
}
using (SqlCommand command = new(s_tableInsertParamQuery, con))
{
command.Parameters.AddWithValue("@", "next data");
command.ExecuteNonQuery();
}
using (SqlCommand command = new(s_tableInsertParamQuery, con))
{
command.Parameters.AddWithValue("@", "next data");
command.ExecuteNonQuery();
}
Console.WriteLine("Table inserted with large data");
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment