4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

BlazorAdvent Calendar 2024

Day 2

Blazor - OnInitializedAsync の途中でもレンダリングはされるし OnAfterRender も呼ばれるし、何なら複数コンポーネントが並列実行されているように見えもするよ

Last updated at Posted at 2024-12-01

OnInitializedAsync の途中でもレンダリングはされる

Blazor のコンポーネントにおけるライフサイクルメソッドのひとつとして、OnInitializedAsync というメソッドがあります。この OnInitializedAsync メソッドは、戻り値に Task を返す async/await 構文で記述できる非同期メソッドで、コンポーネントの初期化のタイミングでいちど呼び出されます。同様のライフサイクルメソッドとして、その同期版である OnInitialized というメソッドもあります。

OnInitializedAsync をオーバーライドすることで、例えば以下のように、OnIntializedAsync メソッド内で、REST API から初期データを取得、みたいなことを実現できます。

何か.razor
@inject HttpClient Http

@if (this._item is null) {
    <p>初期化中です..</p>
}
else {
    <p>初期化完了しました!</p>
}

@code {
    private ToDoItem? _item;

    // コンポーネントの初期化時に呼び出されます
    protected override async Task OnInitializedAsync() {
        _item = await this.Http.GetFromJsonAsync<ToDoItem>("./api/todos/1");
    }
}

ここでちょっと面白いのは、この OnInitializedAsync メソッドは、その中に記述したすべてのコードが実行されるまでレンダリングが待たされるのではなく、最初の await で Task の完了待ちになった時点で、いちどレンダリングが実行される、という点です。

実際、上記コード例でいえば、REST API からの応答が返るまでの間、ブラウザ画面には「初期化中です...」と表示されているのを見ることができます。

movie-001.gif

このことからも、OnInitializedAsync メソッド内の処理が途中であっても、非同期処理の await のタイミングでいったんレンダリングはされている、ということがわかります。

OnInitializedAsync の途中でも OnAfterRender も呼ばれる

いったんレンダリングはされるので、レンダリング完了時のライフサイクルメソッド OnAfterRender (ないしはその非同期版である OnAfterRenderAsync) も、OnInitializedAsync メソッド内の処理がすべて完了する前であっても呼び出されます (下記コード例参照)。

何か.razor
<!-- 省略 -->

@code {
    private ToDoItem? _item;

    protected override async Task OnInitializedAsync() {
        _item = await this.Http.GetFromJsonAsync<ToDoItem>("./api/todos/1");
        
        // 実行がここに到達する前に...
    }

    protected override void OnAfterRender(bool firstRender) {
        // (REST API からの応答を待っている間に) 先にここが呼び出されます!
        if (firstRender) {
            Console.WriteLine("初回レンダリングが完了しました");
        }
    }
}

OnInitializedAsync メソッドは一般に,コンポーネントの初期化処理を記述することが多いので、OnInitializedAsync メソッド内のすべての処理が完了するまでは、初回のレンダリングは行なわれないものと、つい思ってしまうことがあるかもしれませんが、実はそうではない、 ということです。

メソッド名も、よくよく読むと、"On Initialized" と過去形になっており、より厳密に読み取れば「初期化が完了した」あとのタイミングで呼び出されるよ、言い方を変えると、そういう "通知" のメソッドに過ぎないよ、といったことが示唆されていると理解できます。

複数コンポーネントで OnInitializedAsync が並列実行されているように見えることもある

とりわけ Blazor WebAssembly はシングルスレッドで動作しますが (Blazor Server であっても、レンダリングは単一スレッドで行なわれます)、非同期処理が絡むと、複数コンポーネント間において、あたかも OnInitializedAsync が並列実行されているように見えることもあります。

例えば、とある 3 つのコンポーネント、Component1.razorComponent2.razorComponent3.razor があるとして、それらコンポーネントの実装は、みな下記内容となっているとします。

Component1.razor
<fieldset>
    <legend>Component 1</legend>
    <div>
        @(_step switch {
            4 => "OnInitializedAsync 完了しました!",
            _ => $"OnInitializedAsync ステップ {_step}..."
        })
    </div>
    <div class="@($"progress-bar step-{_step}")"></div>
</fieldset>

@code {
    private int _step = 0;

    protected override async Task OnInitializedAsync() {
        _step = 1;
        await Task.Delay(Random.Shared.Next(500, 1000));

        _step = 2;
        StateHasChanged();
        await Task.Delay(Random.Shared.Next(500, 1000));

        _step = 3;
        StateHasChanged();
        await Task.Delay(Random.Shared.Next(500, 1000));

        _step = 4;
    }
}

すなわち、いずれのコンポーネントも、OnInitializedAsync ライフサイクルメソッド内で、ランダムな数百ミリ秒の非同期待機をはさみながら「ステップ 1」「ステップ 2」「ステップ 3」「完了しました!」と表示を切り替える実装となっています。これらコンポーネントすべてを貼り付けたページを用意し (下記実装例)、

何か.razor
<Component1 />
<Component2 />
<Component3 />

実行してブラウザで開いてみると、下図のように、3 つのコンポーネントにおける OnInitializedAsync メソッドが、あたかも並行実行されているかのように見えます。

movie-002.gif

もちろん、マルチスレッドで動作しているわけではなく、あくまでも非同期処理の組み合わせによって、あるコンポーネントが Task.Delay() の完了を await しているタイミングで、別のコンポーネントが Task.Delay() が完了したので処理継続する、といった動作になっているだけです。あるタイミングにおいて、OnInitializedAsync メソッド内の処理が実行されているコンポーネントは、常にひとつだけであり、それはつまり、他のコンポーネントが await で待機状態である間に交代で処理を継続しているだけに過ぎません。

まとめ

同期版の OnIitialized ライフサイクルメソッドについては、その中の処理がすべて完了して返るまでは初回レンダリングされません。そのため、その非同期版である OnIitializedAsync メソッドも、ついうっかり、同じように動作するものと思ってしまうことがあるかもしれません。しかしながら、実際はそうではなく、非同期処理の待機にはいるタイミングでひとまずレンダリングはされてしまいます。

また、非同期処理の待機中は、他のコンポーネントの処理が継続されます。そのため、一見、複数のコンポーネントの各種メソッドが同時に並行実行されているかのように見えてしまうこともあります。しかし実際には、非同期処理の待機と復帰のタイミングで交代しながら実行されているだけです。

ぱっと見た目の印象とは異なる振る舞いですので、つい勘違いしがちではあります。しかしここの認識を誤ったままですと、順列に処理されなければならない初期化処理が前後してしまう、などの不具合にもつながりかねません。気を付けておきたいところですね。

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?