Blazor how-tos: status from a background task
Ever wondered how would it be possible to launch a background task and display its status in your Blazor app? Well, in this post you’ll learn not one, but two ways to do it!
In the last post, we saw how easy it is to implement Conway’s Game of Life using Blazor. The code was registering a Scoped instance of the World class which in turn was broadcasting an event every time the simulation was updated.
For those interested, I used the Scoped lifetime instead of Singleton in order to ensure that every UI client is receiving its own instance. Otherwise, everyone connecting to the app would see the same simulation.
So the first technique is indeed broadcasting the necessary events. The key here is the call to StateHasChanged(). From the docs:
StateHasChanged notifies the component that its state has changed. When applicable, calling
StateHasChanged
causes the component to be rerendered.
Basically, when your UI component is relying on data that gets updated outside, maybe from another thread, the system won’t be able to detect the changes. In that case your only option is to force the re-rendering in order to get the new state on screen.
The call to StateHasChanged() will switch to the correct context and push a request to the Blazor’s rendering queue.
In the UI component, all you have to do is register to the event and call StateHasChanged(). Something like this:
@code{ protected override void OnInitialized() { State.OnChangeAsync += Refresh; } private async Task Refresh() { await InvokeAsync(StateHasChanged); } public void Dispose() { State.OnChangeAsync -= Refresh; } }
It’s very important to unsubscribe from the event in the Dispose() method to avoid memory leaks.
This covers the first technique. The other one instead is a paradigm shift: instead of having the server notifying the client when the data is available, we have the client polling the data at regular intervals. Something like this:
@code{ private System.Timers.Timer _timer; [Parameter] public double Interval { get; set; } protected override void OnInitialized() { _timer = new System.Timers.Timer(this.Interval); _timer.Elapsed += async (s, e) => { await InvokeAsync(StateHasChanged); }; _timer.Enabled = true; } }
The core doesn’t change, we still need to use InvokeAsync + StateHasChanged. But this time all we have to do is just initialize a timer and get the new state in its callback.
As usual, all the code is available on GitHub, feel free to take a look and send me your comments!
In this sample, I’m registering a Background Service that will increment a counter every 500 milliseconds. The Counter has a Value property and exposes also an async Event we can leverage to get notified of status changes.