Home > OS >  Not recognized due to async when switching to the Blazor page
Not recognized due to async when switching to the Blazor page

Time:10-09

This is the case when you switch from the page you use async to another page and come back. When I return, async is constantly working on the console and data is coming in, but StateHasChanged() does not seem to be able to update the screen even if it is working or not. Is there a problem that the page cannot be updated normally due to the problem of separating threads? (StateHasChanged() and other functions) In this case, how can I make the existing thread that I want to recognize?

I searched and there was a word about Synchronization Context, would this help?

@page "/push"

<button  @onclick="@subscribe">subscribe</button>
<div>
  @foreach (var noti in PushNotifications.notifications)
  {
    <p>@noti</p>
  }
</div>

@code {
public async Task subscribe()
{
...
    reply = client.subscribe(subscriptionRequest);

    try
    {
      await foreach (var subscriptionResponse in reply.ResponseStream.ReadAllAsync())
      {
        Console.WriteLine(subscriptionResponse);
        PushNotifications.notifications.Add(subscriptionResponse);

        await InvokeAsync(StateHasChanged);
        await Task.Delay(500);
      }
   }
}

CodePudding user response:

... you use async to another page and come back. When I return, async is constantly working on the console [...] , but StateHasChanged() does not seem to be able to update the screen

The most likely explanation is that you 'return' to a new page but the console is still displaying incoming data on the previous old page. That page is still in memory but not on screen. It should have been cleaned up.

Step 1: When your page code creates resources (like HttpClient) then you need:

@implements IDisposable

@code {
  ...
  
  public void Dispose() 
  { 
     // Dispose resources, make sure your subscription loop is canceled. 
     _httpClient?.Dispose();
     ... 
  } 
}

I searched and there was a word about Synchronization Context, would this help?

No, when running on WebAssembly that context is null.

CodePudding user response:

Here is some code that demonstrates one way to maintain state between pages with a long running process.

Everything takes place in a DI scoped service. In this case it just gets a list of countries slowly. There's two cancellation mechanisms: a cancellation token or a method to set an bool.

public class DataService
{
    public List<string> Countries { get; set; } = new List<string>();
    public Action? CountryListUpdated;
    public bool Processing { get; private set; }=false;
    private bool _cancel = false;
    private CancellationToken _cancellationToken = new CancellationToken();
    private Task? _task;
    public string Message { get; private set; } = "Idle";

    public ValueTask GetCountriesAsync(CancellationToken cancellationToken)
    {
        _cancellationToken = cancellationToken;
        _task = this.getCountriesAsync();
        return ValueTask.CompletedTask;
    }

    public ValueTask GetCountriesAsync()
    {
        _task = this.getCountriesAsync();
        return ValueTask.CompletedTask;
    }

    public async Task getCountriesAsync()
    {
        this.Processing = true;
        this.Message = "Processing";
        this.Countries.Clear();
        foreach (var country in _countries)
        {
            this.Countries.Add(country);
            this.CountryListUpdated?.Invoke();
            await Task.Delay(2500);
            if (_cancellationToken.IsCancellationRequested || _cancel)
                Debug.WriteLine("GetCountries Cancelled");

        }
        this.Message = "Processing Complete";
        this.Processing = false;
        _cancel = false;
        this.CountryListUpdated?.Invoke();
    }

    public void CancelProcessing()
        => _cancel = true;

    private List<string> _countries => new List<string> { "UK", "France", "Portugal", "Spain", "Italy", "Germany"};
   
}

Registered in Program:

// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddSingleton<WeatherForecastService>();
builder.Services.AddScoped<DataService>();

Here's the display page. Note UI updates are event driven.

@page "/"
@inject DataService DataService;
@implements IDisposable

<PageTitle>Index</PageTitle>

<div >
    <button  disabled="@(!this.DataService.Processing)" @onclick=this.CancelProcessing>Cancel Processing</button>
    <button  disabled="@this.DataService.Processing" @onclick=GetData>Get Data</button>
</div>
<div >@this.DataService.Message</div>

@foreach (var country in DataService.Countries)
{
    <div>@country</div>
}


@code {
    private CancellationTokenSource cancellationToken = new CancellationTokenSource();

    protected override void OnInitialized()
        => this.DataService.CountryListUpdated  = OnCountryUpdated;

    private async Task GetData()
    {
        await this.DataService.GetCountriesAsync(cancellationToken.Token);
    }

    private void CancelProcessing()
    {
        //DataService.CancelProcessing();
        this.cancellationToken.Cancel();
    }

    private void OnCountryUpdated()
        => this.InvokeAsync(StateHasChanged);

    public void Dispose()
    {
        // If you want to cancel the processing when you exit a page
        //cancellationToken.Cancel();
        this.DataService.CountryListUpdated -= OnCountryUpdated;
    }
}
  • Related