简体   繁体   English

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

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

I'm messing with sync/async to learn them better and encountered one thing that I can't explain.我正在使用同步/异步来更好地学习它们,并遇到了一件我无法解释的事情。 I've set up Web Api project with two endpoints which read data from the db.我已经设置了带有两个端点的 Web Api 项目,这些端点从数据库中读取数据。 One is sync another one is async.一个是同步的,另一个是异步的。 Both do the same operation that takes ~5s on the database side (intentionally throttled).两者都执行相同的操作,在数据库端需要大约 5 秒的时间(故意限制)。 Also I throttled web server in terms of threads:我还根据线程限制了 Web 服务器:

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

Currenyly I'm worried by synchronous method behaviour, async works as I expect it to work. Currenyly 我担心同步方法的行为, async 像我期望的那样工作。

When I send 3 requests to synchronous endpoint simultaneously, I end up with 2 responses in 5 seconds, and 1 response in 10 seconds, which seems logical to me:当我同时向同步端点发送 3 个请求时,我在 5 秒内得到 2 个响应,在 10 秒内得到 1 个响应,这对我来说是合乎逻辑的:

在此处输入图片说明

However, when I send 4 requests to synchronous endpoint simultaneously, I end up with 4 responses which all take 10 seconds (but I expect 2 to be 5s and other 2 to be 10s)但是,当我同时向同步端点发送 4 个请求时,我最终得到 4 个响应,它们都需要 10 秒(但我预计 2 个是 5s,其他 2 个是 10s)

在此处输入图片说明

In web server logs i see that these 4 requests in fact processed synchronously and response times are ~5s:在 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

Endpoint code:端点代码:

[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";
            }
        }
    }
}

I'm using Fiddler to send requests.我正在使用 Fiddler 发送请求。 Is it something with Fiddler (because logs are ok), or I'm missing something in the understanding of how this works?是 Fiddler 的问题(因为日志没问题),还是我在理解它的工作原理时遗漏了一些东西?

In ASP .NET Core there is a lot of async stuff executed behind the scene.在 ASP .NET Core 中有很多在幕后执行的异步内容。 In particular, before your controller action is executed, a thread must read and parse HTTP request sent by fiddler.特别是,在执行控制器操作之前,线程必须读取和解析 fiddler 发送的 HTTP 请求。 After the action execution, another thread (maybe the same) is needed to write a response back to the fiddler.在动作执行之后,需要另一个线程(可能是相同的)将响应写回给 fiddler。

So what happened in your first case with 3 requests:那么在您的第一个案例中有 3 个请求时发生了什么:

  1. As you allow max 2 threads, two actions were started simultaneously and were blocked immediately by sync call reader.Read() .由于您允许最多 2 个线程,因此同时启动了两个操作,并立即被同步调用reader.Read()阻止。
  2. After ~5s, both threads completed the action GetSyncDb() , but the response has not yet been sent.大约 5 秒后,两个线程都完成了GetSyncDb() ,但尚未发送响应。
  3. Now 2 threads are free, one can handle the 3rd request (and be blocked for ~5s) and the other can be used by ASP to write responses to the client.现在有 2 个线程空闲,一个可以处理第 3 个请求(并被阻塞约 5 秒),另一个可以被 ASP 用来向客户端写入响应。

Thus, 2 requests end after ~5s while the third after ~10s.因此,2 个请求在约 5 秒后结束,而第三个请求在约 10 秒后结束。

in the case of 4 requests:在 4 个请求的情况下:

  1. Two actions were started simultaneously and were blocked immediately by sync call reader.Read() .两个操作同时启动并被同步调用reader.Read()立即阻止。
  2. After ~5s, both threads completed the action GetSyncDb() , but the response has not yet been sent.大约 5 秒后,两个线程都完成了GetSyncDb() ,但尚未发送响应。
  3. The other 2 requests were started and block all available threads.其他 2 个请求已启动并阻止所有可用线程。 ASP doesn't have any worker thread available. ASP 没有任何可用的工作线程。 But it needs them to write response and complete the the first 2 requests .但它需要他们写响应并完成前 2 个请求
  4. After next ~5s, both threads are released and ASP can finally write all responses.在接下来的约 5 秒后,两个线程都被释放,ASP 终于可以写入所有响应。

Therefore all requests end after ~10s.因此,所有请求在约 10 秒后结束。 But of course it is random.但当然它是随机的。 Sometimes ASP can catch the free thread and complete first responses before they are blocked by the other requests.有时 ASP 可以捕获空闲线程并在它们被其他请求阻塞之前完成第一个响应。

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

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