如何修复“当前线程未与渲染器同步上下文相关联”的问题?

41
我正在尝试更改我的Blazor服务器端应用程序中用于标题的字符串。但是,我无法使UI更新。
我尝试使用StateHasChanged(),但这并没有起作用,所以我查找并发现在FlightFinder演示中,它具有一个OnChange事件操作,因此我正在尝试实现它。
它可以工作,直到我尝试刷新浏览器,然后我遇到了这个错误:
System.InvalidOperationException:“当前线程未与渲染器的同步上下文相关联。在触发渲染或修改任何在渲染期间访问的状态时,请使用Invoke()或InvokeAsync()将执行切换到渲染器的同步上下文。”
这是我的代码:
private string _title = "TestSite";
public string Title => _title;

public event Action OnChange;

public void ChangePage(string pageName)
{
   _title = pageName;
   NotifyStateChanged();
}

private void NotifyStateChanged(int navigationType = 0)
{
   OnChange?.Invoke();
}

我所要做的就是调用ChangePage("some Page Title"),它能正常工作,除非像我之前提到的那样尝试刷新。

我只是想通过另一个组件更改一个组件上的字符串,听起来并不那么疯狂。如果有更好的方法来处理标题或从其他组件更改内容,我很乐意听取建议。

那么,我该怎么做才能确保我的调用方法在正确的线程上运行? 还是有其他更有效的更改标题的方法吗?

提前感谢您!


1
由于这是谷歌搜索此错误的首选,我想发布另一个原因和解决方案。如果您已经在回调上调用了InvokeAsync并仍然收到此错误,则是因为引发调用的基础事件被定义为“Action”,而应该是“Func<Task>”。 - clamchoda
4个回答

57

我刚刚实现了一个像这样的状态容器,并遇到了相同的错误 - 但我的服务需要成为单例。 因此,我在aspnetcore git中找到了一个示例,完全按照错误消息所说的做法。 调用InvokeAsync -- 不是从你的状态容器中,而是当你尝试改变你的razor组件的状态时。

https://github.com/dotnet/aspnetcore/blob/321db9d99f84cf7a67d453384292d9339de748d1/src/Components/test/testassets/BasicTestApp/DispatchingComponent.razor

所以你的状态容器不需要改变,只需要改变你的组件事件处理程序。

@code{
    protected override void OnInitialized()
    {
         _YourService.OnChange += OnMyChangeHandler;
    }

    public void Dispose()
    {
         _YourService.OnChange -= OnMyChangeHandler;
    }

    private async void OnMyChangeHandler(object sender, EventArgs e)
    {
        // InvokeAsync is inherited, it syncs the call back to the render thread
        await InvokeAsync(() => {
            DoStuff();
            StateHasChanged();
        });
    }
}

现在,如果您的服务是单例模式,它可以一次性通知所有用户!想想过去我们为了做到这一点需要跳过多少麻烦的步骤。


它能与 public event Action OnChange; 一起工作吗? - nam
1
@bmiller,非常感谢您在这个问题上的答复,它让我一个月前摆脱了困境。我在这个主题中提到了您:https://dev59.com/Dbroa4cB1Zd3GeqPl4Mv#61367495?noredirect=1#comment108570870_61367495 - Nik P
3
我不确定为什么需要使用 lambda 表达式。下面的代码对我来说是有效的: await InvokeAsync(StateHasChanged); - Rich Armstrong
你说得对,lambda并不是必需的。不过我认为这样做可以更清楚地说明发生了什么,并且很可能你的“DoStuff()”代码也需要在Invoke上下文中运行。 - bmiller
@nam,您的服务只需像往常一样调用事件;OnChange?.Invoke(this, EventArgs.Empty); 如果您的服务是单例模式,则每个页面/用户都应该收到该事件。 - bmiller
显示剩余3条评论

20

我一大早发布了这篇文章,本来想着没有时间深入研究,等别人来帮忙时我再花时间去看。但是我已经在这件事上反复了几天。

最终,我找到了这篇文章,它解释了我想做的叫做状态容器。

他们说我可以将该类注入为单例,这也是我一开始的做法,或者作为一个范围服务。结果发现我只需要将其更改为范围服务就可以了,现在它运行得很好!


6
简要了解了作用域和单例后,这很容易理解。我仍然会保留我的问题,因为我认为Blazor的问题和答案不够多,这可能在某个时候对某些人有所帮助。 - DalTron
1
那对我有用!谢谢 - Julian Dormon

19

不需要复杂的解决方案,如果在事件处理程序中更新GUI,则Blazor可以完美工作。

this.InvokeAsync(() => this.StateHasChanged());

2
这有什么不同于OP已经得到的答案吗?(不要管他们基于自己接受的答案,显然更喜欢更改服务类型而不是使用InvokeAsync())。我认为这个答案根本没有用。它对已经存在的信息没有任何贡献。 - Peter Duniho
1
我谈论的不是DalTron的回答。你的“真正有效的代码”与bmiller的答案中发布的代码完全相同,除了另一个答案中他们实际上解释了代码之外,存在一些微小的差异。 - Peter Duniho
这个解决方案并没有解决问题。 - Payedimaunt
3
我点赞了这个回答,因为@Vaicheslav恰好有666分。 - Greg Gum
@PeterDuniho 不需要使用 await 操作符,调用函数也不需要改为 async。 - clamchoda

3

在我的情况下,使用await InvokeAsync(stateHasChanged);可以解决问题。


是的,那只是 InvokeAsync(() => this.StateHasChanged()) 的简短(且更高效)形式。 - H H

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