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

时间:2019-02-02 18:16:16

标签: c# blazor blazor-server-side

从任意线程调用rewind是否安全?

让我给您一些背景信息。想象一下您拥有的服务器端Blazor / Razor组件应用程序:

  • 单例服务StateHasChanged(),它从任意线程引发NewsProvider个事件。
  • 获取服务并订阅BreakingNews事件的组件News.cshtml。引发事件时,组件将更新模型并调用BreakingNews

NewsProvider.cs

StateHashChanged()

News.cshtml

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

Startup.cs

@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"); } } } 是否是线程安全的。如果不是,如何安全地呼叫StateHasChanged()?是否有类似于StateHashChanged()的东西?我应该使用Control.BeginInvoke吗?

2 个答案:

答案 0 :(得分:5)

否,从任意线程调用StateHasChanged()是不安全的。在ASP.NET Core 3.0预览版2上运行该代码将引发以下异常:

  

Microsoft.AspNetCore.Components.Browser.Rendering.RemoteRendererException:   '当前线程未与渲染器的   同步上下文。使用Invoke()或InvokeAsync()进行切换   触发时执行渲染器的同步上下文   渲染或修改渲染期间访问的任何状态。”

调用StateHasChanged()的正确方法如下:

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

但是Invoke已添加到ASP.NET NET Core 3.0预览剃刀组件中,在ASP.NET Core 2.1服务器端Blazor中不可用。

答案 1 :(得分:0)

也许这可以帮助您...

SteveSanderson:

  

现有的UI更新机制是围绕从.NET到JS的一批最小差异进行传输而设计的,因此,将这些差异表示为序列化数据而不是将指针表示为WASM内存空间并不难。我们必须要小心周围的事物,像事件处理,以保证异步不会导致行为与同步单线程模型的不一致性。例如,我们需要将迫使所有事件传递是异步至于JS侧而言,即使在非工作线程的情况下。

来源:https://github.com/aspnet/AspNetCore/issues/5475