在Blazor Server中,是否可以保证js互操作调用的顺序?

时间:2020-06-11 15:11:12

标签: blazor blazor-client-side blazor-jsinterop

鉴于

我有一个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。

1 个答案:

答案 0 :(得分:3)

简短答案:是。

更长的答案:

从C#到JavaScript的调用,反之亦然,立即执行。

然后,由Render Dispatcher处理对C#的调用,这意味着,如果C#代码是同步的(或与异步代码一直等待下去),则每次对C#的调用将在下一个调用开始之前完成。 / p>

如果C#代码是异步的并且不等待异步操作(例如Task.Delay),则Render Dispatcher将在当前方法等待时立即开始运行下一个调用的方法。

虽然多个线程不会并发运行。直到当前分派的任务执行await或完成后,await之后,渲染分派器才会继续执行代码。

有效地,Render Dispatcher序列化对组件的访问,因此一次在任何组件上都只运行一个线程-不管它们上运行了多少线程。

PS:对于外部刺激触发的任何代码(例如,由Singleton服务上的另一个用户线程引发的事件),您都可以使用InvokeAsync(......)进行同样的操作。

如果您想了解有关渲染器工作原理的更多信息,请阅读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中删除了asyncawait,就像这样

window.callbackDotNet = function (objRef, counter) {
    objRef.invokeMethodAsync("CalledBackFromJavaScript", counter);
}

运行它时,您会看到对JavaScript的调用按正确的1..10顺序进行,对C#的回调按正确的1..10顺序进行,但“完成回调”的顺序为2,1。 ,4,3,5,7,6,9,8,10。

这将归因于C#内部.NET调度等,在这些情况下,.NET会在所有await Task.Delay(1000)之后决定下一步执行哪个任务。

如果您在JavaScript中恢复了asyncawait,然后在上一次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

注意:您的示例代码将导致多个用户同时执行静态方法。在您的方案中,您应该调用一个实例方法。