В Blazor Server гарантирован ли порядок вызовов взаимодействия js?

При условии

У меня есть, скажем, обработчики событий javascript onscroll или mousemove, которые вызывают на сервере метод C#:

ТОГДА

Гарантирован ли порядок вызовов методов C# на сервере?

например, следующий javascript:

document.body.addEventListener("scroll",(e) => {
   DotNet.invokeMethodAsync("BlazorSample", "HandleOnScroll", e)
});

и C#

@code {
    [JSInvokable()]
    public static async Task HandleOnScroll()
    {
        // ...
    }
}

Аналогичный вопрос был бы тогда наоборот - вызов из DotNet на JS.

1 ответ

Решение

Краткий ответ: да.

Гораздо более длинный ответ:

Вызовы из C# в JavaScript или наоборот выполняются немедленно.

Обратные вызовы в C# затем обрабатываются диспетчером визуализации, что означает, что если код C# является синхронным (или асинхронным с awaits полностью вниз), то каждый вызов 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 page, поэтому он вызывает этот 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

Если вы удалите async а также await из вашего JavaScript, вот так

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).

Если вы восстановите async а также await в вашем JavaScript и только тогда 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

Примечание: ваш пример кода приведет к тому, что несколько пользователей будут выполнять статический метод одновременно. В вашем сценарии вы должны вызывать метод экземпляра.

Другие вопросы по тегам