繁体   English   中英

同步 Web Api 和响应时间取决于线程和请求计数

[英]Synchronous Web Api and response time depending on the thread and request count

我正在使用同步/异步来更好地学习它们,并遇到了一件我无法解释的事情。 我已经设置了带有两个端点的 Web Api 项目,这些端点从数据库中读取数据。 一个是同步的,另一个是异步的。 两者都执行相同的操作,在数据库端需要大约 5 秒的时间(故意限制)。 我还根据线程限制了 Web 服务器:

ThreadPool.SetMinThreads(2, 2);
ThreadPool.SetMaxThreads(2, 2);

Currenyly 我担心同步方法的行为, async 像我期望的那样工作。

当我同时向同步端点发送 3 个请求时,我在 5 秒内得到 2 个响应,在 10 秒内得到 1 个响应,这对我来说是合乎逻辑的:

在此处输入图片说明

但是,当我同时向同步端点发送 4 个请求时,我最终得到 4 个响应,它们都需要 10 秒(但我预计 2 个是 5s,其他 2 个是 10s)

在此处输入图片说明

在 Web 服务器日志中,我看到这 4 个请求实际上是同步处理的,响应时间约为 5 秒:

 info: Microsoft.AspNetCore.Hosting.Diagnostics[1] Request starting HTTP/1.1 GET http://localhost:57128/test/syncDb info: Microsoft.AspNetCore.Hosting.Diagnostics[1] Request starting HTTP/1.1 GET http://localhost:57128/test/syncDb info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] Executing endpoint 'WebApiAsyncAwait.Controllers.TestController.GetSyncDb (WebApiAsyncAwait)' info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] Executing endpoint 'WebApiAsyncAwait.Controllers.TestController.GetSyncDb (WebApiAsyncAwait)' info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[3] Route matched with {action = "GetSyncDb", controller = "Test"}. Executing controller action with signature System.String GetSyncDb() on controller WebApiAsyncAwait.Controllers.TestController (WebApiAsyncAwait). info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[3] Route matched with {action = "GetSyncDb", controller = "Test"}. Executing controller action with signature System.String GetSyncDb() on controller WebApiAsyncAwait.Controllers.TestController (WebApiAsyncAwait). info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] Executing ObjectResult, writing value of type 'System.String'. info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] Executing ObjectResult, writing value of type 'System.String'. info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[2] Executed action WebApiAsyncAwait.Controllers.TestController.GetSyncDb (WebApiAsyncAwait) in 5008.3885ms info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[2] Executed action WebApiAsyncAwait.Controllers.TestController.GetSyncDb (WebApiAsyncAwait) in 5010.472000000001ms info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] Executed endpoint 'WebApiAsyncAwait.Controllers.TestController.GetSyncDb (WebApiAsyncAwait)' info: Microsoft.AspNetCore.Hosting.Diagnostics[2] Request finished in 5026.9979ms 200 text/plain; charset=utf-8 info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] Executed endpoint 'WebApiAsyncAwait.Controllers.TestController.GetSyncDb (WebApiAsyncAwait)' info: Microsoft.AspNetCore.Hosting.Diagnostics[2] Request finished in 5032.421200000001ms 200 text/plain; charset=utf-8 info: Microsoft.AspNetCore.Hosting.Diagnostics[1] Request starting HTTP/1.1 GET http://localhost:57128/test/syncDb info: Microsoft.AspNetCore.Hosting.Diagnostics[1] Request starting HTTP/1.1 GET http://localhost:57128/test/syncDb info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] Executing endpoint 'WebApiAsyncAwait.Controllers.TestController.GetSyncDb (WebApiAsyncAwait)' info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] Executing endpoint 'WebApiAsyncAwait.Controllers.TestController.GetSyncDb (WebApiAsyncAwait)' info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[3] Route matched with {action = "GetSyncDb", controller = "Test"}. Executing controller action with signature System.String GetSyncDb() on controller WebApiAsyncAwait.Controllers.TestController (WebApiAsyncAwait). info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[3] Route matched with {action = "GetSyncDb", controller = "Test"}. Executing controller action with signature System.String GetSyncDb() on controller WebApiAsyncAwait.Controllers.TestController (WebApiAsyncAwait). info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] Executing ObjectResult, writing value of type 'System.String'. info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] Executing ObjectResult, writing value of type 'System.String'. info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[2] Executed action WebApiAsyncAwait.Controllers.TestController.GetSyncDb (WebApiAsyncAwait) in 5007.105500000001ms info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[2] Executed action WebApiAsyncAwait.Controllers.TestController.GetSyncDb (WebApiAsyncAwait) in 5005.6482000000005ms info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] Executed endpoint 'WebApiAsyncAwait.Controllers.TestController.GetSyncDb (WebApiAsyncAwait)' info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] Executed endpoint 'WebApiAsyncAwait.Controllers.TestController.GetSyncDb (WebApiAsyncAwait)' info: Microsoft.AspNetCore.Hosting.Diagnostics[2] Request finished in 5021.8341ms 200 text/plain; charset=utf-8 info: Microsoft.AspNetCore.Hosting.Diagnostics[2] Request finished in 5023.8319ms 200 text/plain; charset=utf-8

端点代码:

[HttpGet]
[Route("syncDb")]
public string GetSyncDb()
{
    string connStr = @"Server=localhost\SQLEXPRESS;Database=AdventureWorks;Trusted_Connection=True;";
    using (SqlConnection conn = new SqlConnection(connStr))
    {
        conn.Open();
        using (SqlCommand cmd = conn.CreateCommand())
        {
            cmd.CommandText = @"SELECT TOP (1) * FROM [AdventureWorks].[Sales].[CreditCard] WAITFOR DELAY '00:00:05'";

            using (SqlDataReader reader = cmd.ExecuteReader())
            {
                var res = reader.Read();
                return "Ok";
            }
        }
    }
}

我正在使用 Fiddler 发送请求。 是 Fiddler 的问题(因为日志没问题),还是我在理解它的工作原理时遗漏了一些东西?

在 ASP .NET Core 中有很多在幕后执行的异步内容。 特别是,在执行控制器操作之前,线程必须读取和解析 fiddler 发送的 HTTP 请求。 在动作执行之后,需要另一个线程(可能是相同的)将响应写回给 fiddler。

那么在您的第一个案例中有 3 个请求时发生了什么:

  1. 由于您允许最多 2 个线程,因此同时启动了两个操作,并立即被同步调用reader.Read()阻止。
  2. 大约 5 秒后,两个线程都完成了GetSyncDb() ,但尚未发送响应。
  3. 现在有 2 个线程空闲,一个可以处理第 3 个请求(并被阻塞约 5 秒),另一个可以被 ASP 用来向客户端写入响应。

因此,2 个请求在约 5 秒后结束,而第三个请求在约 10 秒后结束。

在 4 个请求的情况下:

  1. 两个操作同时启动并被同步调用reader.Read()立即阻止。
  2. 大约 5 秒后,两个线程都完成了GetSyncDb() ,但尚未发送响应。
  3. 其他 2 个请求已启动并阻止所有可用线程。 ASP 没有任何可用的工作线程。 但它需要他们写响应并完成前 2 个请求
  4. 在接下来的约 5 秒后,两个线程都被释放,ASP 终于可以写入所有响应。

因此,所有请求在约 10 秒后结束。 但当然它是随机的。 有时 ASP 可以捕获空闲线程并在它们被其他请求阻塞之前完成第一个响应。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM