【问题标题】:In Blazor Server, is order of js interop calls guaranteed?在 Blazor Server 中,是否保证 js 互操作调用的顺序?
【发布时间】:2020-10-01 06:47:57
【问题描述】:

鉴于

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

【问题讨论】:

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


    【解决方案1】:

    简短回答:是的。

    更长的答案:

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

    然后由 Render Dispatcher 处理返回到 C# 的调用,这意味着如果 C# 代码是同步的(或一直异步的等待),那么每次对 C# 的调用将在下一个调用开始之前运行到完成。

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

    多个线程不会同时运行。在当前调度的任务执行await 或完成之前,Render Dispatcher 不会在 await 之后继续执行代码。

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

    PS:您可以对任何由外部刺激触发的代码(例如由 Singleton 服务上的另一个用户线程引发的事件)使用 InvokeAsync(......)

    如果您想了解有关 Render Dispatcher 工作原理的更多信息,请阅读 Blazor University 上的 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
    

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

    【讨论】:

    • 好的,我明白了dispatcher的工作原理,但是当从javascript调用C#时,onscroll事件可以随时触发,不会等到promise (await)被解决。
    • 当我从 javascript 函数中删除 await 并且 C# 仍然按顺序执行时,我的回答涵盖了这种情况。您遇到问题了吗?如果是这样,可能是因为您有多个用户同时执行一个静态方法。
    猜你喜欢
    • 1970-01-01
    • 2020-07-15
    • 1970-01-01
    • 1970-01-01
    • 2011-07-13
    • 2023-03-25
    • 2017-07-10
    • 2020-05-27
    • 2021-05-02
    相关资源
    最近更新 更多