Parallel API calls runs sequentially in Google Chrome in WebAssembly if DevTools are closed.
Run multiple parallel tasks in WebAssembly calling, for instance, an API.
Introduce artificial delay on API side to ensure requests are running in parallel.
Combine calls into list of tasks and do await Task.WhenAll(listOfRequests).
Measure total execution time and see it equals to ServerSideDelay*NumberOfRequest in Chrome.
Run the same code with Developer Tools Opened in Chrome - works significantly faster, and it is visible in the network tab that requests are running in parallel.
Run the same code in Safari - works significantly faster.
Runtime Environment:
OS Name: Mac OS X
OS Version: 10.15
OS Platform: Darwin
RID: osx.10.15-x64
Base Path: /usr/local/share/dotnet/sdk/3.1.302/
Host (useful for support):
Version: 3.1.6
Commit: 3acd9b0cd1
.NET Core SDKs installed:
2.2.207 [/usr/local/share/dotnet/sdk]
3.1.101 [/usr/local/share/dotnet/sdk]
3.1.102 [/usr/local/share/dotnet/sdk]
3.1.300 [/usr/local/share/dotnet/sdk]
3.1.302 [/usr/local/share/dotnet/sdk]
.NET Core runtimes installed:
Microsoft.AspNetCore.All 2.2.8 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.All]
Microsoft.AspNetCore.App 2.2.8 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.1 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.2 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.4 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.6 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.NETCore.App 2.2.8 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.1 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.2 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.4 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.6 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
Could you post repro code? I suspect the issue might be in your code
private async Task IncrementCount()
{
currentCount++;
var sw = Stopwatch.StartNew();
var tasks = Enumerable.Range(0, 10).Select(x => Http.GetFromJsonAsync<HelloResponse>("hello")).ToList();
await Task.WhenAll(tasks);
sw.Stop();
queryTime = sw.ElapsedMilliseconds.ToString();
}
Thanks for contacting us.
We're moving this issue to the Next sprint planning milestone for future evaluation / consideration. We will evaluate the request when we are planning the work for the next milestone. To learn more about what to expect next and how this issue will be handled you can read more about our triage process here.
This isn't a problem with task parallelism in Blazor, it is a limitation in Chrome.
Chrome will only let a single request exist for the same URL at a time, so if you make 50 requests for /hello it will only make one request at a time. If however you request /hello?id={x} then it will request them all immediately.
You can prove this restriction also exists in JavaScript like so...
Counter.razor file, and add a button that executes it. private async Task DoJS()
{
queryTime = "calculating";
var sw = Stopwatch.StartNew();
await JSRuntime.InvokeVoidAsync("doRequests", 30);
sw.Stop();
queryTime = sw.ElapsedMilliseconds.ToString();
}
window.doRequests = function (count) {
var promises = [];
for (var i = 0; i < count; i++) {
let current = i;
let promise = fetch("https://localhost:6001/hello").then(response => {
return response.json();
}).then(hello => {
console.log("Finished " + current + " " + hello)
});
promises.push(promise);
}
return Promise.all(promises);
};
This is because Chrome will lock the cache item until the request is complete. If you want to allow concurrent requests to the same URL then you need to ensure no-store is specified for caching.
I proved this in JS by changing the fetch line as follows
let promise = fetch("https://localhost:6001/hello", { "cache": "no-store" }).then(response => {
I've not looked at how to do the same with HttpClient.
Thank you. It is helpful.
@vyacheslav-mikhaylov You are welcome. Should this be closed now as it is not Blazor related?
This is how we can use it with HttpClient
var cacheControl = new CacheControlHeaderValue()
{
NoCache = true
};
builder.Services.AddScoped(
sp => new HttpClient
{
BaseAddress = new Uri("https://localhost:6001"),
DefaultRequestHeaders =
{
CacheControl = cacheControl
}
});
And to enable CacheControl header for CORS of the API side:
app.UseCors(
policy =>
policy.WithOrigins("http://localhost:5000", "https://localhost:5001")
.AllowAnyMethod()
.WithHeaders(HeaderNames.ContentType, HeaderNames.Authorization, HeaderNames.CacheControl)
.AllowCredentials());
@vyacheslav-mikhaylov You are welcome. Should this be closed now as it is not Blazor related?
Sure, let's close it. Thanks again.
Thanks for posting the C# solution. Perhaps you could post this as a question on StackOverflow with an answer too? I'm sure others will spot this at some point.
Most helpful comment
This isn't a problem with task parallelism in Blazor, it is a limitation in Chrome.
Chrome will only let a single request exist for the same URL at a time, so if you make 50 requests for /hello it will only make one request at a time. If however you request /hello?id={x} then it will request them all immediately.
You can prove this restriction also exists in JavaScript like so...
Counter.razorfile, and add a button that executes it.This is because Chrome will lock the cache item until the request is complete. If you want to allow concurrent requests to the same URL then you need to ensure no-store is specified for caching.
I proved this in JS by changing the
fetchline as followsI've not looked at how to do the same with HttpClient.