Я знаю, что вызов метода StateHasChanged()
уведомляет компонент об изменении состояния, и поэтому он должен повторно отрисовываться.
Тем не менее, я также иногда вижу такие вещи, как await InvokeAsync(StateHasChanged)
или await InvokeAsync(() => StateHasChanged())
в чужом коде, но я не уверен, чем они отличаются от StateHasChanged()
и где один из них должен быть предпочтительнее других и почему.
Единственная информация, которую я смог найти, была этой частью документации Blazor, в которой говорилось, что:
Если компонент необходимо обновить на основе внешнего события, такого как таймер или другие уведомления, используйте метод InvokeAsync, который отправляется в контекст синхронизации Blazor.
Я этого не понимаю. Он просто говорит «... который отправляет в контекст синхронизации Blazor», меня это не устраивает, что такое «контекст синхронизации Blazor»?
Я попытался вызвать StateHasChanged()
вместо InvokeAsync(StateHasChanged)
в событии Timer
Elapsed
, и он работает, как и ожидалось, без каких-либо проблем. Мне вместо этого звонить await InvokeAsync(StateHasChanged)
?! И если да, то почему именно? Я чувствую, что, вероятно, здесь есть какой-то важный нюанс, о котором я не знаю.
Я также видел звонки типа InvokeAsync(() => InvokeAsync(Something))
, опять же, почему?
Кроме того, я также иногда вижу, что InvokeAsync()
вызывается без await
, что с этим делать?!
Извините, слишком много вопросов! :D
Я попытался вызвать StateHasChanged() вместо InvokeAsync(StateHasChanged) в событии Timer Elapsed, и он работает, как и ожидалось.
Должно быть, это было на WebAssembly. Когда вы попробуете это на Blazor Serverside, я ожидаю исключения. StateHasChanged() проверяет, работает ли он в правильном потоке.
Основная проблема заключается в том, что рендеринг и вызов StateHasChanged должны происходить в основном (UI) потоке. Виртуальный DOM не является потокобезопасным.
Все основные события жизненного цикла Blazor (OnInit, AfterRender, ButtonClick) выполняются в этом специальном потоке, поэтому в редких случаях, когда вам требуется StateHasChanged(), его можно вызвать без InvokeAsync().
Таймер отличается, это «внешнее событие», поэтому вы не можете быть уверены, что он будет выполняться в правильном потоке. InvokeAsync() делегирует работу Blazor SynchronizationContext, что гарантирует его выполнение в основном потоке.
Но Blazor WebAssembly имеет только 1 поток, поэтому в настоящее время внешние события также всегда выполняются в основном потоке. Это означает, что если вы неправильно воспользуетесь этим паттерном Invoke, вы ничего не заметите. До тех пор, пока однажды Blazor Wasm, наконец, не получит настоящие потоки, ваш код не будет работать. Как и в случае с вашим экспериментом с таймером.
Что такое «контекст синхронизации Blazor»?
В .net контекст синхронизации определяет, что происходит с ожиданием (после). Разные платформы имеют разные настройки, контекст синхронизации Blazor во многом похож на контекст WinForms и WPF. В основном по умолчанию стоит .ConfigureAwait(true)
: возобновить в той же теме.
Иногда я вижу .ConfigureAwait(false)
в коде верхнего уровня Blazor Wasm. Это тоже взорвется, когда мы получим настоящие темы. Его можно использовать в службах, вызываемых из blazor, но не для методов верхнего уровня.
И, наконец, await InvokeAsync(StateHasChanged)
или await InvokeAsync(() => StateHasChanged())
— это просто лямбда-выражения в C#, ничего общего с Blazor. Первая короткая форма немного эффективнее.
Я также иногда вижу, что
InvokeAsync()
называется безawait
Это будет работать. Вероятно, это лучше, чем другой вариант: сделать вызывающий метод (например, OnTick таймера) async void
. Так что используйте это из пути синхронного кода.
Хм, это тест, не знаю, для чего он нужен. Это не «настоящий код».
О, хорошо, приятно знать. Спасибо! Еще одна вещь: вы сказали ...Until one day, when Blazor Wasm finally gets real threads, your code will fail
, это действительно произойдет? Или ожидается, что Blazor WebAssembly всегда останется однопоточным? Что, в свою очередь, означало бы, что InvokeAsync
всегда будет фактически нерелевантным в Blazor WebAssembly и релевантным только на стороне Blazor Sever.
Я думаю, это обсуждается: github.com/dotnet/aspnetcore/discussions/22885
Хорошо, так что это еще не решено. Спасибо!
Тестовый код, кажется, проверяет это, включая обнаружение ошибок на стороне сервера. Он использует различные способы имитации «внешнего» события из внутреннего нажатия кнопки.
@HenkHolterman Если иногда StateHasChanged() вызывает ошибку, не должен ли я всегда использовать await InvokeAsync(StateHasChanged)? Такой как безмозглый вариант. Если нет, то почему?
(Поздний ответ:) Нет, я бы не использовал его всегда. Это добавляет немного накладных расходов, но что более важно: это почти никогда не нужно. Хочу обратить внимание на те частные случаи, где это необходимо. Invoke() — это желтый флаг.
Кстати, это также относится к Blazor Hybrid с WPF. WPF требует, чтобы обновление пользовательского интерфейса выполнялось в основном потоке. Если StatusHasChanged()
вызывается в другом потоке, вместо этого он должен быть заключен в InvokeAsync(StatusHasChanged)
.
Спасибо. Кстати, о вашем заявлении
And finally, await InvokeAsync(StateHasChanged or await InvokeAsync(() => StateHasChanged() is just about lambda's in C#, nothing to do with Blazor.
Но я имел в виду, что на самом деле я вижу вызовInvokeAsync
внутри другого, как в здесь (строка 42)