I have gone through many Stackoverflow threads but I am still not sure why this is taking so long. I am using VS 2022 and it's a console app and the target is .NET 6.0. There is just one file Program.cs where the function and the call to the function is coded.
I am making a GET call to an external API. Since that API returns 10000 rows, I am trying to call my method that calls this API, 2-3 times in Parallel. I also try to update this Concurrent dictionary object that is declared at the top, which I then use LINQ to show some summaries on the UI.
This same external GET call on Postman takes less than 30 seconds but my app takes for ever.
Here is my code. Right now this entire code is in Program.cs of a Console application. I plan to move the GetAPIData() method to a class library after this works.
static async Task GetAPIData(string url, int taskNumber)
{
var client = new HttpClient();
var serializer = new JsonSerializer();
client.Timeout = TimeSpan.FromMilliseconds(Timeout.Infinite);
var bearerToken = "xxxxxxxxxxxxxx";
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {bearerToken}");
using (var stream = await client.GetStreamAsync(url).ConfigureAwait(false))
using (var sr = new StreamReader(stream))
using (JsonTextReader reader = new JsonTextReader(sr))
{
reader.SupportMultipleContent = true;
while (reader.Read())
{
if (reader.TokenType == JsonToken.StartObject)
{
bag.Add(serializer.Deserialize<Stats?>(reader));
}
}
}
}
My calling code:
var taskNumber=1;
var url = "https://extrenalsite/api/stream";
var bag = ConcurrentBag<Stats>();
var task1 = Task.Run(() => GetAPIData(url, bag, taskNumber ));
var task2 = Task.Run(() => GetAPIData(url, bag, taskNumber ));
var task3 = Task.Run(() => GetAPIData(url, bag, taskNumber ));
await Task.WhenAll(task1, task2, task3);
Please let me know why it is taking way too long to execute when I have spawned 3 threads and why it's slow.
Thanks.
CodePudding user response:
you can create a blocking collection, a thread-safe collection, that acts as a buffer between the producers and consumers, to reduce the amount of contention and synchronization overhead involved.
static async Task GetAPIData(BlockingCollection<Stats> bag, string url, int taskNumber)
{
var client = new HttpClient();
var serializer = new JsonSerializer();
client.Timeout = TimeSpan.FromMilliseconds(Timeout.Infinite);
var bearerToken = "xxxxxxxxxxxxxx";
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {bearerToken}");
using (var stream = await client.GetStreamAsync(url).ConfigureAwait(false))
using (var sr = new StreamReader(stream))
using (JsonTextReader reader = new JsonTextReader(sr))
{
reader.SupportMultipleContent = true;
while (reader.Read())
{
if (reader.TokenType == JsonToken.StartObject)
{
bag.Add(serializer.Deserialize<Stats?>(reader));
}
}
}
}
calling code:
var bag = new BlockingCollection<Stats>(new ConcurrentQueue<Stats>());
var taskNumber=1;
var url = "https://extrenalsite/api/stream";
var task1 = Task.Run(() => GetAPIData(bag, url, taskNumber ));
var task2 = Task.Run(() => GetAPIData(bag, url, taskNumber ));
var task3 = Task.Run(() => GetAPIData(bag, url, taskNumber ));
await Task.WhenAll(task1, task2, task3);
bag.CompleteAdding();
foreach (var item in bag.GetConsumingEnumerable())
{
// access the shared data here, for example, updating the summary on the UI
}
CodePudding user response:
If it is not necessary to immediately write to the collection, you can improve performance by collecting the results locally in the task method. This does not need to do thread synchronisation and will therefore be faster:
static async Task<List<Stats>> GetAPIData(string url, int taskNumber)
{
var result = new List<Stats>();
// your code, but instead of writing to `bag`:
list.Add(serializer.Deserialize<Stats>(reader));
// ...
return list;
}
var task1 = Task.Run(() => GetAPIData(url, taskNumber ));
var task2 = Task.Run(() => GetAPIData(url, taskNumber ));
var task3 = Task.Run(() => GetAPIData(url, taskNumber ));
await Task.WhenAll(task1, task2, task3);
var allResults = task1.Result.Concat(task2.Result).Concat(task3.Result);