Rolling in render trees

This is the second blog post in a mini-series on the internals of rendering and components in Blazor. By internals, I mean that we’ll be doing one of my favorite things: reading code and figuring out what it does. As a preface, this blog post assumes that you’re familiar with Blazor.

If you haven’t read the first blog post already, I’d recommend taking a look at it before reading the rest of this post. We’ll be picking up right where we left off. In the last blog post, we discussed the ComponentBase class in Blazor. Each component has a StateHasChanged method that is invoked when the component needs to be rendered. We’ll use this method as a transition from our exploration of ComponentBase to our dive into render trees.

protected void StateHasChanged()
{
    if (_hasPendingQueuedRender)
    {
        return;
    }

    if (_hasNeverRendered || ShouldRender())
    {
        _hasPendingQueuedRender = true;

        try
        {
            _renderHandle.Render(_renderFragment);
        }
        catch
        {
            _hasPendingQueuedRender = false;
            throw;
        }
    }
}

The relevant parts of this method are the invocation of _renderHandle.Render(_renderFragment) in the try-catch block. We’ll start our exploration by looking at render handles.

A render handle is a construct that allows a component to interact with its handler, so it definitely is a great transition from talking about components to talking about rendering. At it’s core, the RenderHandle maintains a reference to two key properties: the renderer and an integer representing the ID of the component the render handle is associated with. The Render method invoked above calls into the AddToRenderQueue method on the Renderer to facilitate rendering.

The RenderHandle class is onl 60-odd lines of code (and fifteen or so if you ignore whitespace nad newlines.) You can check out the code for it here. Logically, the RenderHandle access as a gated bridge between the user-facing world of components and the internals of rendering. It’s a small wrapper around the rendering APIs that are relevant to components without exposing all the rendering internals that we will discuss shortly. For example, because the RenderHandle “bridge” exists, a component doesn’t need to know its component ID in order to render itself. It can make a call out to AddToRenderQueue via StateHasChanged without having to worry about any of the internals.

Biting into the Renderer

Unlike the RenderHandle, the Renderer implementation has a fair amount of logic. After all, that’s where all the fun behind the gated bridge happens! We’ll break into the Renderer by taking a look at the AddToRenderQueue method that’s invoked above.

internal void AddToRenderQueue(int componentId, RenderFragment renderFragment)
{
    Dispatcher.AssertAccess();

    var componentState = GetOptionalComponentState(componentId);
    if (componentState == null)
    {
        return;
    }

    _batchBuilder.ComponentRenderQueue.Enqueue(new RenderQueueEntry(componentState, renderFragment));

    if (!_isBatchInProgress)
    {
        ProcessPendingRender();
    }
}

Let’s break down what is going on here line-by-line.

Dispatcher.AssertAccess()

The Dispatcher is responsible for facilitating event handling and asynchronous invocations within the renderer. Similar to what we discussed earlier about the RenderHandle being the gated bridge between the world of components and the world of rendering, the Dispatcher is the gated bridge between the world of rendering and the world of

The default dispatcher associated with each renderer is a RendererSynchronizationContextDispatcher, which implements a SynchronizationContext. SynchronizationContext allow a developer to dictate how asynchronous operations that are dispatched to to the synchronization context should be handled. For example, the RendererSynchronizationContext will attempt to execute async tasks synchronously if there are no other operations being processed.

var componentState = GetOptionalComponentState(componentId)

A ComponentState object is used to the track the current rendering state of the component. It’s responsible for a few things including:

You’ll notice in the logic above that we don’t invoke any rendering if we don’t have a ComponentState object registered for the current component. The Renderer maintains a dictionary of { componentId: ComponentState } and exposes two variants for accessing a ComponentState by ID.

In the case above, we avoid queueing a new render operation if the component has already been disposed.

_batchBuilder

The _batchBuilder property is an instance of a RenderBatchBuilder associated with the current renderer.

Render batches capture the changeset in a current render operation that will be applied to the DOM. This includes things like:

The _batchBuilder is responsible for maintaining the queue of components to be rendered and the set of modifications to be made. In the renderer, it’s used to:

.ComponentRenderQueue

The ComponentRenderQueue property maintains the set of the rendering events that still need to be completed by the renderer.

.Enqueue(new RenderQueueEntry(componentState, renderFragment))

Before performing a render, we first add it to the render queue. If no renders are currently in progress, we process it immediately. If there is a render in progress, we don’t do anything since the enqueued render batch will be processed in that render step.

ProcessPendingRender()

The ProcessPendingRender method is invoked if we can process remaining renders off the render queue. If the renderer hasn’t been disposed, the method will invoke ProcessRenderQueue.

Conclusion

We’ll dive into the internals of the ProcessRenderQueue method in the next blog post. For now, here’s a quick summary of what we covered: