Skip to content

Control when StateHasChanged has finished #11760

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Stamo-Gochev opened this issue Jul 1, 2019 · 3 comments
Closed

Control when StateHasChanged has finished #11760

Stamo-Gochev opened this issue Jul 1, 2019 · 3 comments
Labels
area-blazor Includes: Blazor, Razor Components

Comments

@Stamo-Gochev
Copy link

Stamo-Gochev commented Jul 1, 2019

Is your feature request related to a problem? Please describe.

In blazor, in scenarios like dynamically initializing components, it will be handy to have an option to pass a "callback" to StateHasChanged instead of putting the logic in a life-cycle method like OnAfterRender. This is somewhat more convenient and easy to read.

For example:

// ParentComponent.razor.cs
public async void OnClick() 
 {

        // create a child component
        RenderFragment += CreateChildComponent;
        StateHasChanged();  // not finished

        // ChildComponentRef is null
        ChildComponentRef.Show();
}

can be written like:

public async void OnClick() 
{
        ...
        RenderFragment += CreateChildComponent;
        StateHasChanged(() => { ChildComponentRef.Show() });  
}

Another option might be to await it, but this implies a new method, e.g. StateHasChangedAsync:

public async void OnClick() 
{
        ...
        RenderFragment += CreateChildComponent;
        await StateHasChangedAsync();
        ChildComponentRef.Show();
}

Currently, I am not able to implement this approach as wrapping the call in a Task.Run():

public async void OnClick() 
{
        ....
        await Task.Run(() => 
       {
             RenderFragment += CreateChildComponent;
             StateHasChanged();  
       }).ContinueWith(() => 
       {
            ChildComponentRef.Show();
       });
}

fails with an exception:

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.

Still, having a callback seems more convenient and it also doesn't introduce a new method.

@Eilon Eilon added the area-blazor Includes: Blazor, Razor Components label Jul 1, 2019
@SteveSandersonMS
Copy link
Member

In blazor, in scenarios like dynamically initializing components, it will be handy to have an option to pass a "callback" to StateHasChanged instead of putting the logic in a life-cycle method like OnAfterRende

I see. Yes, that would be convenient in some cases.

The drawback is that then the framework would have to do some extra internal tracking, so it could remember all the callbacks and their associations with different rendering requests. We'd need some new internal data structures which would involve extra heap allocations during the render cycle.

Since this is an unusual case and handling it comes with real perf drawbacks, I think it's not something we'd be likely to do in the foreseeable future.

Generally there are a couple of more practical ways to deal with initialization of descendants:

  • OnAfterRenderAsync
  • Having code in the descendant components themselves that does whatever you want in their OnInit/OnInitAsync methods (including possibly making calls back to the ancestor component if you need)

If these approaches aren't viable in your case, it would be great to know more about what you're doing. Hope that makes sense!

@Stamo-Gochev
Copy link
Author

Stamo-Gochev commented Jul 2, 2019

Thanks @SteveSandersonMS, the suggested approaches are viable. The idea was about providing an additional convenient way of handling the same, but as this comes at a cost, then OnAfterRenderAsync will be used instead.

Out of curiosity:

Since this is an unusual case and handling it comes with real perf drawbacks, I think it's not something we'd be likely to do in the foreseeable future.

are there any ideas for a general improvement that will probably make the extra heap allocations not that critical according to performance? I suppose that this is very important for the wasm hosting model.

@SteveSandersonMS
Copy link
Member

are there any ideas for a general improvement that will probably make the extra heap allocations not that critical according to performance

GC is always a major consideration for perf on .NET. We've done a lot of work in ASP.NET Core generally, and Blazor specifically, to minimize GC costs, and I expect that to continue indefinitely. Our goal is to make the framework as GC-light as possible so that application developers have more headroom to do what they want that might stress the GC more.

There's probably no magic bullet to fix this, however in the long run GC might get a lot cheaper if WebAssembly itself can provide a managed heap, which is one of the proposals being worked on by the WebAssembly specs group.

@ghost ghost locked as resolved and limited conversation to collaborators Dec 3, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-blazor Includes: Blazor, Razor Components
Projects
None yet
Development

No branches or pull requests

3 participants