Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it safe to call StateHasChanged() from an arbitrary thread?

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:

  • A singleton service NewsProvider that raises BreakingNews events from an arbitrary thread.
  • A component 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()

NewsProvider.cs

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();
        }
    }
}

News.cshtml

@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;
    }
}

Startup.cs

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?

like image 267
Jesús López Avatar asked Feb 02 '19 18:02

Jesús López


People also ask

Is Blazor thread safe?

Note: Blazor WebAssembly apps are single-threaded so do not have to account for thread safety.

What is StateHasChanged Blazor?

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.

What is InvokeAsync Blazor?

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.


1 Answers

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();
    });
}
like image 67
Jesús López Avatar answered Oct 18 '22 23:10

Jesús López