从任意线程调用StateHasChanged()方法是否安全?

25

StateHasChanged() 在任意线程中调用是否安全?

让我给你提供一些背景信息。想象一个服务器端的 Blazor/Razor 组件应用程序,其中你有:

  • 一个单例服务 NewsProvider,它从任意线程触发BreakingNews 事件。
  • 一个组件 News.cshtml,该组件注入了该服务并订阅BreakingNews事件。当事件被触发时,该组件会更新模型并调用 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");
        }
    }
}

看起来是可以工作的,但我不确定StateHasChanged()是否是线程安全的。如果不是,那么如何安全地调用StateHashChanged()呢?是否有类似于Control.BeginInvoke的东西?我应该使用SynchronizationContext.Post吗?

1个回答

42

不,从任意线程调用 StateHasChanged() 是不安全的。

正确的调用 StateHasChanged() 的方式是使用InvokeAsync()

void OnBreakingNews(object sender, BreakingNewsEventArgs e)
{
    InvokeAsync(() => {
        news.Add(e.News);
        StateHasChanged();
    });
}

8
自 ASP.NET Core 3.1 预览版2 开始,ComponentBase 中的 Invoke() 方法已不再可用。应改用 InvokeAsync() 方法。 - Anonymous
我相信,但你知道这是否有官方文件或文章支持吗?我的意思不是让你证明你所说的正确性,而是为了更深入地理解为什么会这样。 - Daniel Vega
@DanielVega 用户界面通常不是线程安全的,Blazor也不例外。 - Jesús López
1
如果 StateHasChanged 不是线程安全的,那么是否应该将其包装在 InvokeAsync 中?这引出了两个问题:1)为什么不允许 InvokeAsync 成为一个扩展方法,2)为什么不直接使 StateHasChanged 是线程安全的呢? - Tod

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接