Is it safe to call StateHasChanged()
from an arbitrary thread?
Let me give you some context. Imagine a Server-side Blazor/Razor Components application where you have:
NewsProvider
that raises BreakingNews
events from an arbitrary thread.News.cshtml
that gets the service injected and subscribes to BreakingNews
event. When the event is raised, the component updates the model and calls StateHashChanged()
using System;
using System.Threading;
namespace BlazorServer.App
{
public class BreakingNewsEventArgs: EventArgs
{
public readonly string News;
public BreakingNewsEventArgs(string news)
{
this.News = news;
}
}
public interface INewsProvider
{
event EventHandler<BreakingNewsEventArgs> BreakingNews;
}
public class NewsProvider : INewsProvider, IDisposable
{
private int n = 0;
public event EventHandler<BreakingNewsEventArgs> BreakingNews;
private Timer timer;
public NewsProvider()
{
timer = new Timer(BroadCastBreakingNews, null, 10, 2000);
}
void BroadCastBreakingNews(object state)
{
BreakingNews?.Invoke(this, new BreakingNewsEventArgs("Noticia " + ++n));
}
public void Dispose()
{
timer.Dispose();
}
}
}
@page "/news"
@inject INewsProvider NewsProvider
@implements IDisposable
<h1>News</h1>
@foreach (var n in this.news)
{
<p>@n</p>
}
@functions {
EventHandler<BreakingNewsEventArgs> breakingNewsEventHandler;
List<string> news = new List<string>();
protected override void OnInit()
{
base.OnInit();
breakingNewsEventHandler = new EventHandler<BreakingNewsEventArgs>(OnBreakingNews);
this.NewsProvider.BreakingNews += breakingNewsEventHandler;
}
void OnBreakingNews(object sender, BreakingNewsEventArgs e)
{
this.news.Add(e.News);
StateHasChanged();
}
public void Dispose()
{
this.NewsProvider.BreakingNews -= breakingNewsEventHandler;
}
}
using Microsoft.AspNetCore.Blazor.Builder;
using Microsoft.Extensions.DependencyInjection;
using BlazorServer.App.Services;
namespace BlazorServer.App
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Since Blazor is running on the server, we can use an application service
// to read the forecast data.
services.AddSingleton<WeatherForecastService>();
services.AddSingleton<INewsProvider, NewsProvider>();
}
public void Configure(IBlazorApplicationBuilder app)
{
app.AddComponent<App>("app");
}
}
}
it apparently works, but I don't know if StateHasChanged()
is thread safe. If it isn't, how can I call StateHashChanged()
safely?. Is there something similar to Control.BeginInvoke
? Should I use SyncrhonizationContext.Post
?
Note: Blazor WebAssembly apps are single-threaded so do not have to account for thread safety.
StateHasChanged notifies the component that its state has changed. When applicable, calling StateHasChanged causes the component to be rerendered. StateHasChanged is called automatically for EventCallback methods. For more information on event callbacks, see ASP.NET Core Blazor event handling.
InvokeAsync() delegates the work to Blazor's SynchronizationContext that will ensure it does run on the main thread. But Blazor WebAssembly only has 1 thread so for the time being external events always run on the main thread too. That means that when you get this Invoke pattern wrong you won't notice anything.
No, calling StateHasChanged()
from an arbitrary thread is not safe.
The correct way to call StateHasChanged()
is by using InvokeAsync()
void OnBreakingNews(object sender, BreakingNewsEventArgs e)
{
InvokeAsync(() => {
news.Add(e.News);
StateHasChanged();
});
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With