GIVEN that
I have let's say an onscroll or mousemove javascript eventhandlers, that invokes a C# method on the server:
THEN
is order of the C# method invocations guaranteed on the server?
e.g following javascript:
document.body.addEventListener("scroll",(e) => {
DotNet.invokeMethodAsync("BlazorSample", "HandleOnScroll", e)
});
and C#
@code {
[JSInvokable()]
public static async Task HandleOnScroll()
{
// ...
}
}
A simmilar question would be then the other way around, calling from DotNet to JS.
Short answer: Yes.
The much longer answer:
Calls from C# into JavaScript, or vice-versa, are executed immediately.
Calls back into C# are then handled by the Render Dispatcher, which means that if the C# code is synchronous (or asynchronous with awaits all the way down) then each call into C# will run to completion before the next one starts.
If the C# code is asynchronous and doesn't wait for an async operation (such as Task.Delay
) then the Render Dispatcher will start to run the next invoked method as soon as the current one awaits.
Multiple threads will not run concurrently though. The Render Dispatcher won't continue the code after an await
until the currently dispatched task does an await
or completes.
Effectively, the Render Dispatcher serialises access to components so that only 1 thread at a time is running on any component - regardless of how many threads are running on them.
PS: You can do the same by using InvokeAsync(......)
for any code that is triggered by an external stimulus such as an event raised by another user's thread on a Singleton service.
If you want more information on how the Render Dispatcher works then read the section Multi-threaded rendering on Blazor University.
Here is some proof:
First, create index.js and make sure it is referenced in your HTML page with <script src="/whatever/index.js"></script>
window.callbackDotNet = async function (objRef, counter) {
await objRef.invokeMethodAsync("CalledBackFromJavaScript", counter);
}
Next, update the Index.razor
page so it invokes that JavaScript and accepts the callback.
@page "/"
@inject IJSRuntime JSRuntime
<button @onclick=ButtonClicked>Click me</button>
@code
{
private async Task ButtonClicked()
{
using (var objRef = DotNetObjectReference.Create(this))
{
const int Max = 10;
for (int i = 1; i < 10; i++)
{
System.Diagnostics.Debug.WriteLine("Call to JS " + i);
await JSRuntime.InvokeVoidAsync("callbackDotNet", objRef, i);
}
System.Diagnostics.Debug.WriteLine("Call to JS " + Max);
await JSRuntime.InvokeVoidAsync("callbackDotNet", objRef, Max);
}
}
[JSInvokable("CalledBackFromJavaScript")]
public async Task CalledBackFromJavaScript(int counter)
{
System.Diagnostics.Debug.WriteLine("Start callback from JS call " + counter);
await Task.Delay(1000).ConfigureAwait(false);
System.Diagnostics.Debug.WriteLine("Finish callback from JS call " + counter);
}
}
This entire chain is awaited, including the JavaScript, so the output will look like this...
Call to JS 1
Start callback from JS call 1
* (one second later)
Finish callback from JS call 1
Call to JS 2
Start callback from JS call 2
* (one second later)
Finish callback from JS call 2
... etc ...
Call to JS 9
Start callback from JS call 9
* (one second later)
Finish callback from JS call 9
Call to JS 10
Start callback from JS call 10
* (one second later)
Finish callback from JS call 10
If you remove the async
and await
from your JavaScript, like so
window.callbackDotNet = function (objRef, counter) {
objRef.invokeMethodAsync("CalledBackFromJavaScript", counter);
}
When you run it you will see the calls into JavaScript were in the correct 1..10 order, and the callbacks into C# were in the correct 1..10 order, but the "Finish callback" order was 2,1,4,3,5,7,6,9,8,10.
This will be due to C# internal .NET scheduling etc, where .NET decides which task to pick up next after they all await Task.Delay(1000)
.
If you restore the async
and await
in your JavaScript and then only await
on the last C# call, like so:
using (var objRef = DotNetObjectReference.Create(this))
{
const int Max = 10;
for (int i = 1; i < 10; i++)
{
System.Diagnostics.Debug.WriteLine("Call to JS " + i);
_ = JSRuntime.InvokeVoidAsync("callbackDotNet", objRef, i);
}
System.Diagnostics.Debug.WriteLine("Call to JS " + Max);
await JSRuntime.InvokeVoidAsync("callbackDotNet", objRef, Max);
}
Then you will see the following output:
Call to JS 1
Call to JS 2
Call to JS 3
Call to JS 4
Start callback from JS call 1
Start callback from JS call 2
Start callback from JS call 3
Start callback from JS call 4
* (one second later)
Finish callback from JS call 1
Finish callback from JS call 2
Finish callback from JS call 3
Finish callback from JS call 4
Note: Your sample code would result in multiple users executing a static method at the same time. In your scenario you should be calling an instance method.