1

我正在使用使用 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(); 
            }
        }
    }
}
4

1 回答 1

0

从场景 1 和 2 的结果来看,我猜对 DB 的查询比 HTTP 请求完成得更快(当然,因为本地磁盘的延迟比 Internet 少)。因此,他们通常会在 10 秒内完成。但是你提交的任务有一半是 HTTP 请求,所以你一半的计算能力都在忙着,DB 所需的时间从 10 秒翻倍到 20 秒。在此期间,CPU 使用率为 0.7,这是可实现的最大使用率(因此池实际上是有效的,因为它最大限度地提高了资源利用率,即使 50% 的任务只使用 30% 的 CPU)。

然后,DB 请求完成,只剩下 HTTP 请求。虽然 DB 和 HTTP 是同时执行的,但只有一半的资源专用于 HTTP,所以这 20 秒等于 10 秒的非竞争执行,剩下的 30 秒-10 秒 = 20 秒的执行。

即使 CPU 利用率不是 100%,任务管理器也不会创建 40k 线程,因为那会破坏操作系统。

于 2013-10-19T00:57:03.283 回答