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。

4

1 回答 1

3

简短的回答:是的。

更长的答案:

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

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

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

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

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

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

如果您想了解有关 Render Dispatcher 如何工作的更多信息,请阅读Blazor University 上的多线程渲染部分。

这里有一些证据:

首先,创建 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中删除asyncand ,像这样await

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 中恢复asyncandawait然后仅await在最后一次 C# 调用中恢复,如下所示:

        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

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

于 2020-06-11T17:01:26.020 回答