Параллельный запрос в ASP.NET Web API 2 почти в шесть раз медленнее, чем один запрос - PullRequest
0 голосов
/ 27 августа 2018

Фон:

Мы создали React SPA, который взаимодействует с серверной частью ASP.NET Web API 2. NET Framework 4.6.1. При нашей начальной загрузке мы делаем два отдельных запроса на загрузку данных. При загрузке большого количества данных мы заметили, что запросы API были значительно медленнее, когда мы делали их в приложении, чем когда мы пробовали запросы по отдельности в Postman.

Оригинал:

Пример структуры, fetch использует наш 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, и IIS должен поддерживать 10 одновременных подключений в соответствии с Интернетом. В любом случае это не имеет значения, потому что мы размещаем его в Azure как Windows Server и у нас там одинаковое время отклика. Пробовал также и службу приложений, и результат был тот же.

Тестирование времени отклика синхронно с почтальоном Runner

enter image description here

Два экземпляра Бегуна Почтальона, идущие одновременно:

enter image description here

Другие примечания:

Не похоже, что аппаратное обеспечение связано с тем, что ЦП, память и диск не заслуживают внимания, если запросы выполняются одновременно или нет.

Что мы можем сделать, чтобы это исправить?

Не асинхронный метод, который запускается параллельно:

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

Обновление Async и 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 голосов
/ 29 августа 2018

В конце концов, часть мэра оказалась связанной с базой данных. Метод использовал db.Cases.Find(bsid, caseId), а затем преобразовал модель в модель представления. Модель Cases, в свою очередь, имела много связей, и все они вызывали отдельный вызов базы данных, потому что у модели были свойства, помеченные как virtual, например, public virtual TPRenewalCycle TPRenewalCycle { get; set; } для обеспечения отложенной загрузки. Нашел его, посмотрев в окно вывода Visual Studio (Debug -> Windows -> Output) и установив 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;
    }
...