Skip to content
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

Blazor Webassembly - StateAsChanged not fire using IAsyncEnumerable #43098

Closed
1 task done
GLuca74 opened this issue Aug 4, 2022 · 6 comments
Closed
1 task done

Blazor Webassembly - StateAsChanged not fire using IAsyncEnumerable #43098

GLuca74 opened this issue Aug 4, 2022 · 6 comments
Labels
area-blazor Includes: Blazor, Razor Components feature-blazor-wasm This issue is related to and / or impacts Blazor WebAssembly ✔️ Resolution: Answered Resolved because the question asked by the original author has been answered. question Status: Resolved

Comments

@GLuca74
Copy link

GLuca74 commented Aug 4, 2022

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

Hello,

I am trying to use a controller that returns an IAsyncEnumerable from a webassembly Razor application.

In this repo https://github.com/GLuca74/TestBlazorWebAssembyIAsyncEnumerable there is a simply Web Api Application with the WeatherForecastController that have the Get method that resutrns 1 milion of int as IAsyncEnuverable

    [ApiController]
    [Route("[controller]")]
    public class WeatherForecastController : ControllerBase
    {
        [HttpGet]
        public async IAsyncEnumerable<int> Get()
        {
            for (int i = 0; i < 1000000; i++)
            {
                yield return i;
            }
        }
    }

In the Balzor webassembly application, the counter Page contain the code that use the controller :

<h1>Counter</h1>
<span>@Count</span>
<table class="table">
        <thead>
            <tr>
                <th>Title</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var n in Source) 
            {
                <tr>
                    <td>@n</td>
                </tr>
            }
        </tbody>
    </table>


@code {
    private int Count;
    public List<int> Source;

    protected async override Task OnInitializedAsync()
    {
        await base.OnInitializedAsync();
        Source = new List<int>();
        await foreach (int n in GetData())
        {
            Count = Count + 1;
            Source.Add(n);
            if ((Count % 10) == 0)
            {
                //await Task.Delay(1);
                InvokeAsync(StateHasChanged); // StateHasChanged();
            }
        }
    }

    public async IAsyncEnumerable<int> GetData(CancellationToken cancellationToken = default)
    {
        using (HttpClient httpClient = new HttpClient())
        {
            httpClient.BaseAddress = new Uri(@"https://localhost:7034/");
            httpClient.DefaultRequestHeaders.Accept.Clear();
            httpClient.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
            httpClient.Timeout = new TimeSpan(1, 0, 0);
            HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "weatherforecast");
            request.SetBrowserResponseStreamingEnabled(true);
            using (var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false))
            {
                if (!response.IsSuccessStatusCode)
                {
                    HttpRequestException ex = new HttpRequestException(response.Content.ReadAsStringAsync().GetAwaiter().GetResult(), null, response.StatusCode);
                    throw ex;
                }
                Stream responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
                IAsyncEnumerable<int> intResponse = JsonSerializer.DeserializeAsyncEnumerable<int>(responseStream, new JsonSerializerOptions{PropertyNameCaseInsensitive = true}, cancellationToken);
                await foreach (int n in intResponse)
                {
                    yield return n;
                }
            }
        }
    }
}

When the data is loaded, only one update of the UI is performed and the page remain locked. But if I call await Task.Delay(1) before call StateHasChanged all works and any update of the UI is performed.

Can I ask why the Delay is needed to let it works or wath is wrong with my code?

Expected Behavior

No response

Steps To Reproduce

No response

Exceptions (if any)

No response

.NET Version

No response

Anything else?

No response

@TanayParikh TanayParikh added area-blazor Includes: Blazor, Razor Components feature-blazor-wasm This issue is related to and / or impacts Blazor WebAssembly question labels Aug 4, 2022
@javiercn
Copy link
Member

javiercn commented Aug 5, 2022

@GLuca74 thanks for contacting us.

This is by design. ComponentBase can only observe the task returned by OnInitializedAsync, etc. Not the Tasks in between.

You need to do something like this to trigger renders yourself for the intermediate asynchronous steps:

private static async IAsyncEnumerable<T> WithReRenders<T>(IAsyncEnumerable<T> enumerable, int numberOfItems)
{
    var i = 0;
    await foreach (var text in TextService())
    {
        i = ++i % numberOfItems;
        yield text;
       if(i == 0){
           StateHasChanged();
       }
    }   
}
await foreach (var text in WithReRenders(yourEnumerable,1))
{
...
}

@javiercn javiercn added the ✔️ Resolution: Answered Resolved because the question asked by the original author has been answered. label Aug 5, 2022
@ghost ghost added the Status: Resolved label Aug 5, 2022
@javiercn
Copy link
Member

javiercn commented Aug 5, 2022

@guardrex can we add a sections for this to the docs?

@javiercn
Copy link
Member

javiercn commented Aug 5, 2022

@guardrex could we make an explicit mention to IAsyncEnumerable there?

@GLuca74
Copy link
Author

GLuca74 commented Aug 5, 2022

My apologies, but I not understand the given solution.
The WithReRenders is static, how can I call the not static StateAsChanged?
TextService() wath is? My GetData? I have to call two times GetData?

@ghost
Copy link

ghost commented Aug 6, 2022

This issue has been resolved and has not had any activity for 1 day. It will be closed for housekeeping purposes.

See our Issue Management Policies for more information.

@ghost ghost closed this as completed Aug 6, 2022
@ghost ghost locked as resolved and limited conversation to collaborators Sep 5, 2022
This issue was closed.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-blazor Includes: Blazor, Razor Components feature-blazor-wasm This issue is related to and / or impacts Blazor WebAssembly ✔️ Resolution: Answered Resolved because the question asked by the original author has been answered. question Status: Resolved
Projects
None yet
Development

No branches or pull requests

4 participants