我正在使用使用 C#5 的 async/await 构造编写的简单 C# 控制台应用程序进行一些基准测试,并且数字不会相加(事实上它们相加,这就是问题所在;))
我正在对三种不同的场景进行基准测试:1)对 SQL 服务器存储过程的 20K 调用。2) 对简单 HTTP 服务器的 20K 调用 3) 场景 1) 和 2) 一起
以下是有关场景的更多详细信息:
1) 对 SQL 服务器存储过程的 20K 调用。
在这种情况下,我调用外部 SQL Server 存储过程 20K 次。CallSqlStoredProcedureAsync 方法使用 ADO.NET 异步方法(OpenAsync、ExecuteNonQueryAsync ...)。我什至在方法入口处等待 Task.Yield() 以避免在到达异步点之前执行任何同步代码,以避免在最短时间内阻塞我的循环。
var tasks = new List<Task>();
for (int i=0; i<20000; i++)
{
tasks.Add(this.CallSqlStoredProcedureAsync());
}
Task.WhenAll(tasks).Wait();
这在大约10 秒内完成,平均 CPU 消耗为 70%。
2) 对一个简单的 HTTP 服务器的 20K 调用
在这种情况下,我也使用 HttpClient 和异步方法(PostAsync)在外部网络服务器上调用 url。
for (int i=0; i<20000; i++)
{
tasks.Add(this.SendRequestToHttpServerAsync());
}
这在大约30 秒内完成,平均 CPU 消耗为 30%
3) 场景 1) 和 2) 一起
for (int i=0; i<20000; i++)
{
tasks.Add(this.CallSqlStoredProcedureAsync());
tasks.Add(this.SendRequestToHttpServerAsync());
}
这在大约40 秒内完成,大约 20 秒的 CPU 消耗平均为 70%,其余 20 秒为 30%。
现在的问题
我不明白为什么场景 #3 的基准测试需要 40 秒。如果执行是顺序的,或者如果我的 CPU(或 I/O)对于场景 1 和 2 是 100%,我会说场景 1 的时间 + 场景 2 的时间是正常的。
考虑到我将使用 async/await 构造完全异步,我对场景 #3 的期望是它在 30 秒内完成(“链中最薄弱的环节”),即场景 #2 的持续时间。
这里有一些我不明白的地方:(
有什么线索吗?
编辑:根据@svick 请求,这是基准测试的完整代码(不包括一些无用的东西)
static void Main(string[] args)
{
var bench = new Bench();
while (true)
{
string iterationsAndScenario = Console.ReadLine();
var iterations = int.Parse(iterationsAndScenario.Split(' ')[0]);
var scenario = int.Parse(iterationsAndScenario.Split(' ')[1]);
var sw = new Stopwatch();
sw.Start();
bench.Start(iterations, scenario).Wait();
sw.Stop();
Console.WriteLine("Bench too {0} ms", sw.EllapsedMilliseconds);
}
}
public class Benchmark
{
public Task Start(int iterations, int scenario)
{
var tasks = new List<Task>();
if (scenario == 1)
{
for (int i=0; i<iterations; i++)
{
tasks.Add(this.CallSqlStoredProcedureAsync().ContinueWith(t => Console.WriteLine(t.Exception), TaskContinuationOptions.OnlyOnFaulted));
}
}
else if (scenario == 2)
{
var httpClient = new HttpClient();
for (int i=0; i<iterations; i++)
{
tasks.Add(httpClient.PostAsync(uri, new StringContent("Hello")).ContinueWith(t => Console.WriteLine(t.Exception), TaskContinuationOptions.OnlyOnFaulted));
}
}
else if (scenario == 3)
{
var httpClient = new HttpClient();
for (int i=0; i<iterations; i++)
{
tasks.Add(this.CallSqlStoredProcedureAsync().ContinueWith(t => Console.WriteLine(t.Exception), TaskContinuationOptions.OnlyOnFaulted));
tasks.Add(httpClient.PostAsync(uri, new StringContent("Hello")).ContinueWith(t => Console.WriteLine(t.Exception), TaskContinuationOptions.OnlyOnFaulted));
}
}
return Task.WhenAll(tasks);
}
public async Task CallSqlStoredProcedureAsync()
{
await Task.Yield();
using (var conn = new SqlConnection(connectionString))
{
using (var cmd = new SqlCommand("sp_mystoreproc", conn))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("@param1", 'A');
cmd.Parameters.AddWithValue("@param2", 'B');
await cmd.Connection.OpenAsync();
await cmd.ExecuteNonQueryAsync();
}
}
}
}