What are the benefits of native *Async
methods available in the System.Data.SqlClient
namespace? What are their advantages over a manual Task.Run
with a body comprised of only synchronous method calls?
Here's my 'starting point' example (console application):
using System;
using System.Data.SqlClient;
using System.Threading.Tasks;
class Program
{
const string CommandTest = @"
SET NOCOUNT ON;
WITH
L0 AS (SELECT c FROM (SELECT 1 UNION ALL SELECT 1) AS D(c)), -- 2^1
L1 AS (SELECT 1 AS c FROM L0 AS A CROSS JOIN L0 AS B), -- 2^2
L2 AS (SELECT 1 AS c FROM L1 AS A CROSS JOIN L1 AS B), -- 2^4
L3 AS (SELECT 1 AS c FROM L2 AS A CROSS JOIN L2 AS B), -- 2^8
L4 AS (SELECT 1 AS c FROM L3 AS A CROSS JOIN L3 AS B), -- 2^16
L5 AS (SELECT 1 AS c FROM L4 AS A CROSS JOIN L4 AS B), -- 2^32
Nums AS (SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS k FROM L5)
SELECT
k
FROM
Nums
WHERE
k <= 1000000";
const string ConnectionString = "Server=.;Database=master;Integrated Security=SSPI;";
// This requires c# 7.1 or later. Check project settings
public static async Task Main(string[] args)
{
var aSW = new System.Diagnostics.Stopwatch();
aSW.Restart();
{
var aRes = ExecuteSync();
Console.WriteLine($"ExecuteSync returned {aRes} in {aSW.Elapsed}.");
}
aSW.Restart();
{
var aRes = await ExecuteWrapperAsync();
Console.WriteLine($"ExecuteWrapperAsync returned {aRes} in {aSW.Elapsed}.");
}
aSW.Restart();
{
var aRes = await ExecuteNativeAsync();
Console.WriteLine($"ExecuteNativeAsync returned {aRes} in {aSW.Elapsed}.");
}
}
private static Task<long> ExecuteWrapperAsync()
{
return Task.Run(() => ExecuteSync());
}
private static long ExecuteSync()
{
using (var aConn = new SqlConnection(ConnectionString))
using (var aCmd = new SqlCommand(CommandTest, aConn))
{
aConn.Open();
using (var aR = aCmd.ExecuteReader())
{
long aRetVal = 0;
while (aR.Read())
aRetVal += aR.GetInt64(0);
return aRetVal;
}
}
}
private static async Task<long> ExecuteNativeAsync()
{
using (var aConn = new SqlConnection(ConnectionString))
using (var aCmd = new SqlCommand(CommandTest, aConn))
{
await aConn.OpenAsync();
using (var aR = await aCmd.ExecuteReaderAsync())
{
long aRetVal = 0;
while (await aR.ReadAsync())
aRetVal += aR.GetInt64(0);
return aRetVal;
}
}
}
}
Speaking about performance on my development maching, usage of the *Async
methods actually resulted in slower running times. Typically, my output was as follows:
ExecuteSync returned 500000500000 in 00:00:00.4514950.
ExecuteWrapperAsync returned 500000500000 in 00:00:00.2525898.
ExecuteNativeAsync returned 500000500000 in 00:00:00.3662496.
In other words, the method ExecuteNativeAsync
is the one using the *Async
methods of System.Data.SqlClient
and was most often slower than a synchronous method wrapped by a Task.Run
call.
Am I doing something wrong? Maybe I am mis-reading the documentation?