I have an application that needs to contact a remote API many times to request information about various products. It was taking around a minute with synchronous programming. I adjusted all my methods to be async and I still do not see a performance enhancement. I suspect it's because I'm awaiting each result from the API instead of running them all in parallel.
The code works but it just does not work well. I'm really looking to learn and understand how to develop async methods the correct way.
Here is my API wrapper (if you guys have any suggestions for this too I'd love to hear it):
public class InventoryAPI
{
private RestClient _client;
private RestRequest request;
public InventoryAPI(RestClient client)
{
this._client = client;
}
public async Task<RootLotsObject> GetInventory(string company, string buyer, string vendCode, string lotNum, string prodNum, string status)
{
request = new RestRequest("ppro/services", Method.POST);
var cancelTokenSource = new CancellationTokenSource();
request.AddParameter("appId", "APIINV");
request.AddParameter("command", "getInventory");
request.AddParameter("username", "jraynor");
if (company != null) { request.AddParameter("company", company); }
if (buyer != null) { request.AddParameter("buyer", buyer); }
if (vendCode != null) { request.AddParameter("vendor", vendCode); }
if (lotNum != null) { request.AddParameter("lotNum", lotNum); }
if (prodNum != null) { request.AddParameter("prodNum", prodNum); }
if (status != null) { request.AddParameter("status", status); }
IRestResponse<RootLotsObject> response = await client.ExecuteTaskAsync<RootLotsObject>(request, cancelTokenSource.Token);
request = null;
return response.Data;
}
}
Here I call the API wrapper:
public LoadSchedulingRepository(RestClient client)
{
db = new LoadEntities();
invAPI = new InventoryAPI(client);
}
public async Task<IEnumerable<InventoryProdAndSubsVM>> GetInventoryForOrdersAPIAsync(string prodNum, DateTime? userDate)
{
var invResults = await invAPI.GetInventory("0009", null, null, null, prodNum, "O");
if (invResults != null)
{
var results = invResults.responseData.lots.Where(x => x.ExcludeFIFOAlloc == "N").GroupBy(l => l.productNum).Select(x => new InventoryProdAndSubsVM
{
ProdDesc = x.Max(y => y.productDesc),
ProdCode = x.Max(y=>y.product),
ProdNum = x.Key,
Committed = invResults.responseData.lots.Where(w => w.ReceiveDate != null).Sum(z => Convert.ToDecimal(z.commitQty)),
DueIn = invResults.responseData.lots.Where(w => w.ExpectedDate == userDate).Sum(z => Convert.ToDecimal(z.orderedQty)),
OnHand = x.Sum(y => y.OnHand),
Condition = x.Max(y => y.condition),
Commodity = x.Max(y => y.commodity)
});
return results;
}
return null;
}
Here is my bottle neck method I believe:
private async Task<List<InventoryProdAndSubsVM>> GetProductInventoryAsync(IEnumerable<BackhaulTopVM> prodCodes, DateTime userDate)
{
//Only take unique ProdCodes
foreach (var product in prodCodes.GroupBy(x => x.ProdNum).Select(x => x.FirstOrDefault()))
{
//Get inventory for specific product code and append it to a list
//BOTTLE NECK HERE;
itemsToAdd = await loadSchedulingRepo.GetInventoryForOrdersAPIAsync(product.ProdNum, userDate);
foreach (var item in itemsToAdd)
{
//Create a new list item and add it to resultSet
var currentInventory = new InventoryProdAndSubsVM
{
Commodity = item.Commodity,
DueIn = item.DueIn,
OnHand = item.OnHand,
Committed = item.Committed,
ProdCode = item.ProdCode,
ProdNum = item.ProdNum,
ProdDesc = item.ProdDesc
};
//Append to initalized list
resultSet.Add(currentInventory);
}
}
return resultSet;
}
Here I'm awaiting loadSchedulingRepo.GetInventoryForOrdersAPIAsync
and I think it needs to complete a web request for every single ProdCode
in prodCodes
.
Is there a way to run every rest request in parallel or something?
I was toying around with this:
var prodCodeTasks = prodCodes.Select(x => loadSchedulingRepo.GetInventoryForOrdersAPIAsync(x.ProdNum, userDate)).ToList();
await Task.WhenAll(prodCodeTasks);
client = new RestClient("https://pproqa.xxxxxxxxx.com");
is being created in a loop and stored as field of the class. It'll be overwritten if called by multiple threads. Also if this is creating aHttpClient
internally you might run out of sockets if you fire too many requests. \$\endgroup\$