BTOM
BTOM

Reputation: 73

Blazor Server Problem that page update by Timer stops

I have written a program that updates pages regularly with the Blazor server app.

When this program is executed, the current time and the value from the database table are acquired every second and displayed on the page, but the processing in this timer stops after a certain amount of time.

Although it is not measured accurately, the processing in the timer stops after about 10 to 30 minutes.

However, when I look at the debug tool of the browser, there is no error, and there is no error on visual studio.

I searched for an example like this but couldn't find it and couldn't determine what was causing it.

I want the processing in this timer to work until I explicitly specify it as stopped.

What could be the cause of this? I'm in trouble because I don't know the cause. Please give me some advice. Thank you.

    private string Time { get; set; }

    protected override void OnInitialized()
    {
        var timer = new System.Threading.Timer((_) =>
        {
            Time = DateTime.Now.ToString();
            InvokeAsync(() =>
            {   
                // Get the value from the database
                databaseValue = TimerProcessGetValue();                
                StateHasChanged();
            });
        }, null, 0, 1000);
        base.OnInitialized();
    }

Process called in Timer

public async Task<int?> TimerProcessGetValue()
{
    int? timerProcessValue;
    using(DbContext = DbFactory.CreateDbContext())
    {
        timerProcessValue = dbContext.TestTable.Single(x => x.Id == 1).TestValue;
    }
    return timerProcessValue;
}

postscript

The full text of the target component.

PageRefleshTest.cs

@page "/refresh-ui-manually"
@using System.Threading;
@using TestBlazorServer.Models;
@using Microsoft.EntityFrameworkCore;
@inject IDbContextFactory<SQLbeginnerContext> DbFactory

<h1>@Time</h1> 
<h1>TestValue:@databaseValue</h1>

private string Time { get; set; }
private int? databaseValue { get; set; }

protected override void OnInitialized()
{
    var timer = new System.Threading.Timer((_) =>
    {
        Time = DateTime.Now.ToString();
        InvokeAsync(() =>
        {
            // Get the value from the database
            databaseValue = TimerProcessGetValue().Result;
            StateHasChanged();
        });
    }, null, 0, 1000);
    base.OnInitialized();
}


public async Task<int?> TimerProcessGetValue()
{
    int? timerProcessValue;
    using (var dbContext = DbFactory.CreateDbContext())
    {
        timerProcessValue = dbContext.TestTable.Single(x => x.Id == 1).TestValue;
    }
    return timerProcessValue;
}

Implemented using System.Timers.Timer

@page "/Refresh2"
@inject IDbContextFactory<SQLbeginnerContext> DbFactory
@using TestBlazorServer.Models
@using Microsoft.EntityFrameworkCore
@using System.Timers
@implements IDisposable

<h3>PageRefresh2</h3>
<h1>@Time</h1>
<h1>@currentCount</h1>
<h1>TestValue:@databaseValue/h1>

@code {

private int currentCount = 0;
private Timer timer2 = new(1000);
private string Time { get; set; }
private int? databaseValue { get; set; }

protected override void OnInitialized()
{
    timer2.Elapsed += (sender, eventArgs) => OnTimerCallback();
    timer2.Start();
}

private void OnTimerCallback()
{
    _ = InvokeAsync(() =>
    {
        currentCount++;
        Time = DateTime.Now.ToString();
        databaseValue = TimerProcessGetValue().Result;
        StateHasChanged();
    });
}


public void Dispose() => timer2.Dispose();


public async Task<int?> TimerProcessGetValue()
{
    int? timerProcessValue;
    await using (var dbContext = DbFactory.CreateDbContext())
    {
        timerProcessValue = dbContext.TestTable.Single(x => x.Id == 1).TestValue;
    }
    return timerProcessValue;
}

}

enter image description here

Upvotes: 2

Views: 1803

Answers (1)

Henk Holterman
Henk Holterman

Reputation: 273244

It could of course be from the SignalR connection failing. That should reconnect however.

The main suspect here is .Result. You should always avoid .Wait() and .Result in async code. Aslo, your DB method is not really async, that might play a role too.

But that 'half hour' could also come from the Garbage collector throwing out the Timer object. See the purple Note on this page. When you have a Timer you should anchor it in a field and Dispose() it.

The next step is to make the eventhandler an async void, this is one of the very few cases that is called for. And then only Invoke the StatehasChanged call.

@implements IDisposable

...
@code {
  System.Threading.Timer _timer;  // use a private field
  protected override void OnInitialized()
  {
    _timer = new System.Threading.Timer(async (_) =>
    {
        Time = DateTime.Now.ToString();
        databaseValue = await TimerProcessGetValue();
        await InvokeAsync(StateHasChanged);    
    }, null, 0, 1000);
    // base.OnInitialized();  -- not needed
  }

  public void Dispose() 
  {
    _timer?.Dispose();
  }
}

And make the Db action actually async:

public async Task<int?> TimerProcessGetValue()
{
    int? timerProcessValue;
    using (var dbContext = DbFactory.CreateDbContext())
    {
        timerProcessValue = 
          (await dbContext.TestTable.SingleAsync(x => x.Id == 1))
          .TestValue;
    }
    return timerProcessValue;
}

Only you can test this in-situ, let us know if it works.

Upvotes: 5

Related Questions