在 Blazor Server 中,是否保证 js 互操作调用的顺序?
In Blazor Server, is order of js interop calls guaranteed?
鉴于
我假设有一个 onscroll 或 mousemove javascript 事件处理程序,它在服务器上调用 C# 方法:
然后
服务器保证 C# 方法调用的顺序?
例如以下 javascript:
document.body.addEventListener("scroll",(e) => {
DotNet.invokeMethodAsync("BlazorSample", "HandleOnScroll", e)
});
和 C#
@code {
[JSInvokable()]
public static async Task HandleOnScroll()
{
// ...
}
}
一个类似的问题会反过来,从 DotNet 调用 JS。
简短回答:是。
更长的答案:
从 C# 到 JavaScript 的调用会立即执行,反之亦然。
返回 C# 的调用然后由渲染调度程序处理,这意味着如果 C# 代码是同步的(或一直异步等待),那么每次调用 C# 都将 运行 在下一个开始。
如果 C# 代码是异步的并且不等待异步操作(例如 Task.Delay
),那么渲染分派器将在当前当前调用后立即开始 运行 下一个调用的方法一个等待。
多个线程不会同时运行。渲染调度程序不会在 await
之后继续代码,直到当前调度的任务执行 await
或完成。
实际上,Render Dispatcher 序列化了对组件的访问,因此一次只有 1 个线程在任何组件上 运行ning - 不管有多少线程在它们上 运行ning。
PS:您可以对任何由外部刺激触发的代码使用 InvokeAsync(......)
来执行相同的操作,例如由另一个用户的线程在 Singleton 服务上引发的事件。
如果您想了解有关渲染调度程序如何工作的更多信息,请阅读 Blazor 大学的 Multi-threaded rendering 部分。
这里有一些证明:
首先,创建 index.js 并确保在您的 HTML 页面中使用 <script src="/whatever/index.js"></script>
引用它
window.callbackDotNet = async function (objRef, counter) {
await objRef.invokeMethodAsync("CalledBackFromJavaScript", counter);
}
接下来,更新 Index.razor
页面,使其调用 JavaScript 并接受回调。
@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);
}
}
等待整个链,包括 JavaScript,因此输出将如下所示...
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
如果您从 JavaScript 中删除 async
和 await
,就像这样
window.callbackDotNet = function (objRef, counter) {
objRef.invokeMethodAsync("CalledBackFromJavaScript", counter);
}
当您 运行 它时,您会看到对 JavaScript 的调用是在正确的 1..10 顺序中,并且对 C# 的回调是在正确的 1..10 顺序中,但是"Finish callback" 顺序是 2,1,4,3,5,7,6,9,8,10。
这将是由于 C# 内部 .NET 调度等原因,其中 .NET 决定在完成所有任务后接下来要执行哪个任务 await Task.Delay(1000)
。
如果您在 JavaScript 中恢复 async
和 await
,然后仅在最后一次 C# 调用中恢复 await
,如下所示:
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);
}
然后你会看到如下输出:
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
注意:您的示例代码会导致多个用户同时执行一个静态方法。在您的场景中,您应该调用实例方法。
鉴于
我假设有一个 onscroll 或 mousemove javascript 事件处理程序,它在服务器上调用 C# 方法:
然后
服务器保证 C# 方法调用的顺序?
例如以下 javascript:
document.body.addEventListener("scroll",(e) => {
DotNet.invokeMethodAsync("BlazorSample", "HandleOnScroll", e)
});
和 C#
@code {
[JSInvokable()]
public static async Task HandleOnScroll()
{
// ...
}
}
一个类似的问题会反过来,从 DotNet 调用 JS。
简短回答:是。
更长的答案:
从 C# 到 JavaScript 的调用会立即执行,反之亦然。
返回 C# 的调用然后由渲染调度程序处理,这意味着如果 C# 代码是同步的(或一直异步等待),那么每次调用 C# 都将 运行 在下一个开始。
如果 C# 代码是异步的并且不等待异步操作(例如 Task.Delay
),那么渲染分派器将在当前当前调用后立即开始 运行 下一个调用的方法一个等待。
多个线程不会同时运行。渲染调度程序不会在 await
之后继续代码,直到当前调度的任务执行 await
或完成。
实际上,Render Dispatcher 序列化了对组件的访问,因此一次只有 1 个线程在任何组件上 运行ning - 不管有多少线程在它们上 运行ning。
PS:您可以对任何由外部刺激触发的代码使用 InvokeAsync(......)
来执行相同的操作,例如由另一个用户的线程在 Singleton 服务上引发的事件。
如果您想了解有关渲染调度程序如何工作的更多信息,请阅读 Blazor 大学的 Multi-threaded rendering 部分。
这里有一些证明:
首先,创建 index.js 并确保在您的 HTML 页面中使用 <script src="/whatever/index.js"></script>
window.callbackDotNet = async function (objRef, counter) {
await objRef.invokeMethodAsync("CalledBackFromJavaScript", counter);
}
接下来,更新 Index.razor
页面,使其调用 JavaScript 并接受回调。
@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);
}
}
等待整个链,包括 JavaScript,因此输出将如下所示...
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
如果您从 JavaScript 中删除 async
和 await
,就像这样
window.callbackDotNet = function (objRef, counter) {
objRef.invokeMethodAsync("CalledBackFromJavaScript", counter);
}
当您 运行 它时,您会看到对 JavaScript 的调用是在正确的 1..10 顺序中,并且对 C# 的回调是在正确的 1..10 顺序中,但是"Finish callback" 顺序是 2,1,4,3,5,7,6,9,8,10。
这将是由于 C# 内部 .NET 调度等原因,其中 .NET 决定在完成所有任务后接下来要执行哪个任务 await Task.Delay(1000)
。
如果您在 JavaScript 中恢复 async
和 await
,然后仅在最后一次 C# 调用中恢复 await
,如下所示:
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);
}
然后你会看到如下输出:
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
注意:您的示例代码会导致多个用户同时执行一个静态方法。在您的场景中,您应该调用实例方法。