*Async
名称空间中可用的本机System.Data.SqlClient
方法有什么好处?与仅由同步方法调用组成的正文的手册Task.Run
相比,它们有什么优势?
这是我的“起点”示例(控制台应用程序):
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;
}
}
}
}
谈到我的开发流程时的性能,实际上使用*Async
方法会导致运行时间变慢。通常,我的输出如下:
ExecuteSync returned 500000500000 in 00:00:00.4514950.
ExecuteWrapperAsync returned 500000500000 in 00:00:00.2525898.
ExecuteNativeAsync returned 500000500000 in 00:00:00.3662496.
换句话说,方法ExecuteNativeAsync
是使用*Async
的{{1}}方法的方法,通常比System.Data.SqlClient
调用包装的同步方法要慢。
我做错什么了吗?也许我误读了文档?
答案 0 :(得分:1)
在几乎所有情况下,无论您使用同步SqlClient还是异步SqlClient API,对于查询运行时,聚合资源利用率,应用程序吞吐量或可伸缩性,绝对没有有意义的影响。
一个简单的事实是,您的应用程序可能不会进行数千个并发SQL Server调用,因此为每个SQL查询阻塞线程池线程并不是什么大问题。通过消除请求量中的峰值甚至可以是有益的。
如果要从单个线程协调多个SQL Server调用,API很有用。例如,您可以轻松地对N个SQL Server中的每一个启动查询,然后对结果进行Wait()。
在现代ASP.NET中,您的控制器和几乎所有的API调用都是异步的,并且在UI应用程序中使用Async方法很有用,它可以避免阻塞UI线程。
答案 1 :(得分:1)
要了解Async的好处,您需要使用繁重的异步操作来模拟负载较重的服务器,这需要一些时间才能完成。在没有编写两个版本的情况下,几乎不可能衡量在生产环境中运行的应用程序的收益。
您可以模拟预期的查询延迟,而不必再次调用没有负载并且可能位于应用程序本地的数据库。
随着客户端数量或操作时间的增加ExecuteAsync
将大大胜过ExecuteSync
。在没有负载的情况下,没有观察到使用Async的好处,这通常是在大多数服务器上运行的大多数应用程序的情况。
这里异步的好处是它将线程释放回池中,直到异步操作完成,从而释放了系统资源。
测试程序:
static void Main(string[] args)
{
RunTest(clients: 10, databaseCallTime: 10);
RunTest(clients: 1000, databaseCallTime: 10);
RunTest(clients: 10, databaseCallTime: 1000);
RunTest(clients: 1000, databaseCallTime: 1000);
}
public static void RunTest(int clients, int databaseCallTime)
{
var aSW = new Stopwatch();
Console.WriteLine($"Testing {clients} clients with a {databaseCallTime}ms database response time.");
aSW.Restart();
{
Task.WaitAll(
Enumerable.Range(0, clients)
.AsParallel()
.Select(_ => ExecuteAsync(databaseCallTime))
.ToArray());
Console.WriteLine($"-> ExecuteAsync returned in {aSW.Elapsed}.");
}
aSW.Restart();
{
Task.WaitAll(
Enumerable.Range(0, clients)
.AsParallel()
.Select(_ => Task.Run(() => ExecuteSync(databaseCallTime)))
.ToArray());
Console.WriteLine($"-> ExecuteSync returned in {aSW.Elapsed}.");
}
Console.WriteLine();
Console.WriteLine();
}
private static void ExecuteSync(int databaseCallTime)
{
Thread.Sleep(databaseCallTime);
}
private static async Task ExecuteAsync(int databaseCallTime)
{
await Task.Delay(databaseCallTime);
}
我的结果:
Testing 10 clients with a 10ms database response time.
-> ExecuteAsync returned in 00:00:00.1119717.
-> ExecuteSync returned in 00:00:00.0268717.
Testing 1000 clients with a 10ms database response time.
-> ExecuteAsync returned in 00:00:00.0593431.
-> ExecuteSync returned in 00:00:01.3065965.
Testing 10 clients with a 1000ms database response time.
-> ExecuteAsync returned in 00:00:01.0126014.
-> ExecuteSync returned in 00:00:01.0099419.
Testing 1000 clients with a 1000ms database response time.
-> ExecuteAsync returned in 00:00:01.1711554.
-> ExecuteSync returned in 00:00:25.0433635.
答案 2 :(得分:0)
我已经修改了上面的示例,并能够真正受益于使用*Async
方法:
using System;
using System.Data.SqlClient;
using System.Linq;
using System.Threading.Tasks;
class Program
{
const string CommandTest = @"
SET NOCOUNT ON;
WAITFOR DELAY '00:00:01';
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 <= 100000";
const string ConnectionString = "Server=tcp:.;Database=master;Integrated Security=SSPI;";
const int VirtualClientCount = 100;
// 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 aTasks = Enumerable.Range(0, VirtualClientCount).Select(_ => ExecuteWrapperAsync());
await Task.WhenAll(aTasks);
Console.WriteLine($"ExecuteWrapperAsync completed in {aSW.Elapsed}.");
}
aSW.Restart();
{
var aTasks = Enumerable.Range(0, VirtualClientCount).Select(_ => ExecuteNativeAsync());
await Task.WhenAll(aTasks);
Console.WriteLine($"ExecuteNativeAsync completed 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;
}
}
}
}
现在我得到以下输出:
ExecuteWrapperAsync completed in 00:00:09.6214859.
ExecuteNativeAsync completed in 00:00:02.2103956.
感谢大卫·布朗的提示!