Рубрики
Blazor

Проблемы асинхронного лайфтайма Blazor

За долгое время работы с Blazor, начиная с самых ранних сборок и заканчивая .NET Core 3.1.0 мне пришлось достаточно детально рассмотреть процесс взаимодействия с этим фреймом.

И наверно самым главным элементом в данной тематике можно выделить лайфтайм компонента ComponentBase. Если вы хоть раз заглядывали в этот абстрактный интерфейс, то вы наверняка знаете про три метода и три асинхронные перегрузки этих методов. (OnInitialized, OnParametersSet, OnAfterRender).

Не заглядывая в документацию можно с легкостью ответить, за что ответственен каждый из этих методов.

Первый вызывается сразу же, как только пользователь обратился к конкретной странице с указанным компонентом. Класс отвечающий за рендер сразу же вызывает этот метод у корневых компонентов. На данном этапе никто не гарантирует, что компонент получил все параметры (проперти с аттрибутом [Parameter]).

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

После того, как на корневом элементе успешно завершена инициализация с присвоением всех параметров, затем происходит инициализация вложенного компонента. В этом компоненте сразу же начинается инициализация параметров и параллельно этому вызывается метод OnInitalized(). После успешного присвоения всех параметров, в компоненте также вызывается метод OnParametersSet().

Как видно из скриншота исследования лайфтайма (https://git.defend.pw/defend/blazor-lifetime), далее происходит первый рендер наших синхронных компонентов. Этот рендер всегда вызывается автоматически, как только все компоненты завершили свои процессы инициализации.

По сути, до вызова метода происходит опрос востребованных переменных и воссоздание первого DOM-дерева. И только после того, как данные были отправлены на клиент, происходит вызов OnAfterRender(true).

Все дальнейшие вызовы OnAfterRender будут происходить с флагом false, вне зависимости, отрабатывает ли у вас биндинг или вы сами вызываете StateHasChanged(), до тех пор, пока компонент не будет высвобожден.

blazor-lifetime 
CD 
blazor-lifetime 
Home 
Counter 
Fetch data 
A Not secure 
heps: localhost:5001/counter 
Counter 
Current count: 0 
Click me 
First Component Start 
Second Level Component 
Parameter Value: 123 
First Component End 
blazor- lifetime 
info : 
info : 
info : 
info : 
info : 
Microsoft . Hosting. Lifetime [O] 
Now listening on: 
https : / / localhost : 5001 
Microsoft . Hosting. Lifetime [O] 
Now listening on: 
http://localhost : 5000 
Microsoft . Hosting. Lifetime [O] 
Application started. Press Ctrl+C to shut down. 
Microsoft . Hosting. Lifetime [O] 
Hosting environment: Development 
Microsoft . Hosting. Lifetime [O] 
Content root path: C: 
FirstLeve1Component . Onlni tialized. Start 
F i rs tLeve IComponent . Onlni t ia 1 i zed. End 
FirstLeve1Component . OnParametersSet . Start 
FirstLeve1Component . OnParametersSet . End 
SecondLeve1Component . Parameter. set 
SecondLeve1Component . Onlni tialized. Start 
SecondLeve IComponent . Onlni t i a 1 i zed. End 
SecondLeve1Component . OnParametersSet . Start 
SecondLeve1Component . OnParametersSet . End 
SecondLeve1Component . Parameter. get 
FirstLeve1Component . OnAfterRender (FirstRender) . Start 
FirstLeve1Component . OnAfterRender (FirstRender) . End 
SecondLeve1Component . OnAfterRender (FirstRender) . Start 
SecondLeve1Component . OnAfterRender (FirstRender) . End
Исследование синхронного лайфтайма Blazor-фреймоврка.

Сейчас кто-то может отметить, что сейчас были описаны практически очевидные вещи и он будет абсолютно прав. Здесь у меня нет никаких вопросов к логике лайфтайма. Все загвоздки начнутся далее.

Давайте теперь рассмотрим аналогичный код и для асинхронных версий методов. Ведь вы в любом случае столкнетесь с необходимостью асинхронного обращения к JsInvoke для вызова js-метода (в данном случае нельзя синхронно вызывать js-скрипт). Или же необходимо ввести какую-нибудь задержку. Обратиться асинхронно к базе данных.

Кейсов асинхронного взаимодействия — куча.

В этом случае я буду использовать практически такой же код, но в качестве задержек буду использовать await Task.Delay().

blazor-lifetime 
CD 
blazor-lifetime 
Home 
Counter 
Fetch data 
Not secure 
blazor-lifetime 
heps: localhost:5001/counter 
Content root path: C: 
F i rs tLeve IComponentAsync. Onlni t ia 1 i zed. S ta rt 
SecondLeve1ComponentAsync. Parameter. set (value: ) 
SecondLeve IComponentAsync. Onlni t ia 1 i zed. S t a rt 
SecondLeve1ComponentAsync. Parameter. get (value: ) 
FirstLeve1ComponentAsync. OnAfterRender (FirstRender) . Start 
SecondLeve1ComponentAsync. OnAfterRender (FirstRender) . Start 
Counter 
Current count: 0 
Click me 
First Component Start 
Second Level Component 
Parameter Value: value 123 
First Component End 
SecondLeve IComponentAsync. Onlni t ia 1 i zed. End 
SecondLeve1ComponentAsync. OnParametersSet . Start 
SecondLeve1ComponentAsync. Parameter. get (value : 
F i rs tLeve IComponentAsync. Onlni t ia 1 i zed. End 
FirstLeve1ComponentAsync. OnParametersSet . Start 
SecondLeve1ComponentAsync. Parameter. set (value : 
SecondLeve1ComponentAsync. OnParametersSet . Start 
SecondLeve1ComponentAsync. Parameter. get (value : 
SecondLeve1ComponentAsync. Parameter. get (value : 
value 123) 
value 123) 
value 123) 
FirstLeve1ComponentAsync. OnAfterRender (FirstRender) . End 
SecondLeve1ComponentAsync. OnAfterRender (FirstRender) . End 
SecondLeve1ComponentAsync. OnAfterRender. Start 
FirstLeve1ComponentAsync. OnAfterRender. Start 
SecondLeve1ComponentAsync. OnAfterRender. Start 
SecondLeve IComponentAsync. OnPa rame tersSe t . End 
SecondLeve1ComponentAsync. Parameter. get (value : 
SecondLeve1ComponentAsync. Parameter. get (value : 
SecondLeve IComponentAsync. OnAfterRender. End 
SecondLeve IComponentAsync. OnPa rame tersSe t . End 
SecondLeve1ComponentAsync. Parameter. get (value : 
SecondLeve1ComponentAsync. Parameter. get (value : 
SecondLeve IComponentAsync. OnAfterRender. End 
SecondLeve1ComponentAsync. OnAfterRender. Start 
F i rs tLeve IComponentAsync. OnPa rame tersSe t . End 
FirstLeve IComponentAsync. OnAfterRender. End 
SecondLeve1ComponentAsync. OnAfterRender. Start 
FirstLeve1ComponentAsync. OnAfterRender. Start 
SecondLeve IComponentAsync. OnAfterRender. End 
SecondLeve IComponentAsync. OnAfterRender. End 
FirstLeve IComponentAsync. OnAfterRender. End 
value 123) 
value 123) 
value 123) 
value 123)
Исследование асинхронного лайфтайма Blazor-фреймоврка.

И вот ровно с этого момента начинается ад. Вне зависимости от конфигурации сервера, эта цепочка ведет себя именно так. Происходит инициализация первого компонента, затем сразу же второго, затем сразу же происходит попытка рендера.

Затем у каждого компонента в отдельности происходит вызов OnParameterSet. И хочется отметить самое важное. У второго компонента вызов этого метода уже произошел, однако значение внутреннего параметра еще не было установлено. Хотя для этого метода четко обозначено то же самое определение.

Попытка накатать issue в официальный репозиторий ASP.NET не дала результатов. Команда разработчиков Blazor считает, что данный фреймворк работает так как надо. Однако в асинхронном кейсе, при использовании данного лайфтайма, ты никогда не будешь уверен в том, что все параметры внешнего компонента будут адекватно проинициализированы.

Да, в моем приведенном случае компонент успешно перерендерил значение null в value. Но это произошло успешно, потому что я предварительно сделал проверку на null.

В следующей части статьи я приведу пример решения (костыля), чтобы использовать асинхронные кейсы на лету, не боясь получить NullReferenceException, а также расскажу еще несколько деталей о компонентах в Blazor, которые существенно повлияют на подход к разработке в данной среде, в целом.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *