问题
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 raisesBreakingNews
events from an arbitrary thread. - A component
News.cshtml
that gets the service injected and subscribes toBreakingNews
event. When the event is raised, the component updates the model and callsStateHashChanged()
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
?
回答1:
No, calling StateHasChanged()
from an arbitrary thread is not safe. Running that code on ASP.NET Core 3.0 preview 2 throws the following exception:
Microsoft.AspNetCore.Components.Browser.Rendering.RemoteRendererException: 'The current thread is not associated with the renderer's synchronization context. Use Invoke() or InvokeAsync() to switch execution to the renderer's synchronization context when triggering rendering or modifying any state accessed during rendering.'
The correct way to call StateHasChanged()
is as follows:
void OnBreakingNews(object sender, BreakingNewsEventArgs e)
{
Invoke(() => {
news.Add(e.News);
StateHasChanged();
});
}
But Invoke
was added to ASP.NET NET Core 3.0 preview Razor Components, it is not available on ASP.NET Core 2.1 Server Side Blazor.
来源:https://stackoverflow.com/questions/54496040/is-it-safe-to-call-statehaschanged-from-an-arbitrary-thread