ASP.NET Web API 2中的并发请求比单个请求慢近六倍

时间:2018-08-27 15:00:53

标签: c# asp.net iis concurrency iis-10

背景:

我们已经构建了一个与ASP.NET Web API 2后端。NET Framework 4.6.1通信的React SPA。在初始加载时,我们将发出两个单独的请求以加载数据。当加载大量数据时,我们注意到与在Postman中单独尝试请求相比,在应用程序中创建API请求的速度都慢得多。

原始

示例结构,提取当然使用我们的API:

fetch('http://example.com/movies.json')
  .then(function(response) {
    return response.json();
  })
  .then(function(myJson) {
    console.log(JSON.stringify(myJson));
  });

fetch('http://example.com/otherMovies.json')
  .then(function(response) {
    return response.json();
  })
  .then(function(myJson) {
    console.log(JSON.stringify(myJson));
  });

示例C#API方法:

[HttpGet]
[Route("{bsid}/{caseId}")]
public IHttpActionResult GetRenewalCycleForCustomer(string bsid, int caseId)
{
    var customerNumbers = GetCustomerNumbers();

    var userId = HttpContext.Current.User.Identity.GetUserId<int>();

    var user = identityDb.Users.Find(userId);

    var customerNumbers = user.ApplicationUserCustomerNumbers.Select(x => new CustomerNumberKey() { bsid = x.CustomerNumber.bsid, NameNo = x.CustomerNumber.NameNo }).ToList();

    var db = new DbContext();

    var caseService = new CaseService(db);

    var portfolioTabViewModel = caseService.GetPortfolioTabViewModelForCustomer(bsid, caseId, customerNumbers);

    return Ok(portfolioTabViewModel);
}

操作系统是Windows 10 Pro,根据Internet,IIS应该能够处理10个并发连接。没关系,因为我们将其作为Windows Server托管在Azure上,并且那里的响应时间相同。也尝试了App Service,结果是相同的。

与Postman Runner同步测试响应时间

enter image description here

两个Postman Runner实例同时运行:

enter image description here

其他说明:

似乎与硬件无关,因为无论是否同时发出请求,CPU,内存和磁盘都不会受到明显影响。

该如何解决?

似乎异步运行的非异步方法:

https://stackoverflow.com/a/26737847/3850405

某些线程建议使用会话状态,但是在Web.config中找不到对此的任何引用,并且我没有启用任何引用。搜索HttpContext.Current.SetSessionStateBehavior得到0个结果。

https://stackoverflow.com/a/26172317/3850405

Windows 10资源:

https://serverfault.com/a/800518/293367

https://forums.asp.net/t/2100558.aspx?concurrent+connections+on+windows+pro+10+IIS

更新异步和IIS:

它似乎与异步或IIS不相关,已通过以下方法测试了并发请求。并发的慢速请求似乎还取决于其他因素。

异步:

[HttpGet]
[Route("{bsid}/{caseId}")]
public async Task<IHttpActionResult> Get(string bsid, int caseId)
{
    await Task.Delay(3000);
    return Ok();
}

enter image description here

同步:

[HttpGet]
[Route("{bsid}/{caseId}")]
public IHttpActionResult Get(string bsid, int caseId)
{
    Thread.Sleep(3000);
    return Ok();
}

enter image description here

更新2:具有异步和同步调用的数据库:

似乎也不是数据库调用。使用Include进行的测试在速度上相似,尽管异步调用的速度相当慢。

异步:

[HttpGet]
[Route("{bsid}/{caseId}/async")]
public async Task<IHttpActionResult> GetAsync(string bsid, int caseId)
{
    var db = new DbContext();

    var deviations = await db.RenewalCycles.Where(x => x.Deviations.Any())
        .Include(cycle => cycle.TPCase.CaseNames.Select(caseName => caseName.TPName))
        .Include(cycle => cycle.TPCase.CaseNames.Select(caseName => caseName.TPNameType))
        .Include(cycle => cycle.TPCase.GoodsAndServicesDescriptions)
        .Include(cycle => cycle.TPCase.RelatedCases.Select(relatedCase => relatedCase.TPCaseRelation))
        .Include(cycle => cycle.TPCase.RelatedCases.Select(relatedCase => relatedCase.TPCountry))
        .ToListAsync();

    return Ok();
}

enter image description here

同步:

[HttpGet]
[Route("{bsid}/{caseId}")]
public IHttpActionResult Get(string bsid, int caseId)
{
    var db = new DbContext();

   var deviations = db.RenewalCycles.Where(x => x.Deviations.Any())
        .Include(cycle => cycle.TPCase.CaseNames.Select(caseName => caseName.TPName))
        .Include(cycle => cycle.TPCase.CaseNames.Select(caseName => caseName.TPNameType))
        .Include(cycle => cycle.TPCase.GoodsAndServicesDescriptions)
        .Include(cycle => cycle.TPCase.RelatedCases.Select(relatedCase => relatedCase.TPCaseRelation))
        .Include(cycle => cycle.TPCase.RelatedCases.Select(relatedCase => relatedCase.TPCountry))
        .ToList();

    return Ok();
}

enter image description here

1 个答案:

答案 0 :(得分:0)

毕竟市长的开销部分与数据库有关。一种方法使用db.Cases.Find(bsid, caseId),然后将模型转换为视图模型。 Cases模型又有很多关系,由于模型具有标记为virtual的属性,所有这些关系都需要发出单独的数据库调用,例如public virtual TPRenewalCycle TPRenewalCycle { get; set; }这样就可以进行延迟加载。通过查看Visual Studio输出窗口(调试-> Windows->输出)并按如下所示设置ApplicationDbContext来找到它。

public class ApplicationDbContext : DbContext
{
    protected const string ConnectionStringName = "defaultConnection";
    public ApplicationDbContext() : base(ConnectionStringName)
    {
        Database.Log = s => System.Diagnostics.Debug.WriteLine(s);
        Database.CommandTimeout = 300;
    }