Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the best way to use PeriodicTimer for refresh component in blazor server

I have a component that needs to display fresh data every 1 minute.

I tried the new timer (Periodic Timer) and it works fine.

Now, my questions are,

Where is the best place to put the while loop?

Is something else required for a proper dispose of the PeriodicTimer?

public partial class Index:IDisposable
{
    private readonly PeriodicTimer _periodicTimer = new(TimeSpan.FromMinutes(1));

    protected override async Task OnInitializedAsync()
    {
        await Service.Init();
        while (await _periodicTimer.WaitForNextTickAsync())
        {
            await Service.GetViewModels();
            await InvokeAsync(StateHasChanged);
        }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
           _periodicTimer.Dispose();
        }
    }
}
like image 704
Lucian Bumb Avatar asked Jan 23 '26 16:01

Lucian Bumb


1 Answers

If I put the code in Index, i.e. the statup page, it never completes loading.

If I put the code in another page, it looks like it running fine but it isn't.

The problem is that OnInitializedAsync never completes. It's always awaiting in the while loop. Therefore the rest of the initial component lifecycle doesn't complete either.

As a startup page, it gets locked in the initial server load.

To solve the problem you need to "fire and forget" the timer loop.

Here's a demo component that works, and how to use the "older" timer with an event handler (which I personally would stick with).

@page "/Test"
@implements IDisposable

<h1>Hello</h1>
<div class="m-2">
    Message: @message
</div>
<div class="m-2">
   Timer Message: @timerMessage
</div>
<div class="m-2">
    @state
</div>
@code {
    private System.Timers.Timer timer = new System.Timers.Timer(2000);
    private readonly PeriodicTimer _periodicTimer = new(TimeSpan.FromSeconds(5));
    private string message = "Starting";
    private string timerMessage = "Starting";
    private string state = "On Init Running";

    // This is broken code 
    //protected override async Task OnInitializedAsync()
    //{
    //    while (await _periodicTimer.WaitForNextTickAsync())
    //    {
    //        message = $"Updated at {DateTime.Now.ToLongTimeString()}";
    //        await InvokeAsync(StateHasChanged);
    //    }
    //}

    protected override void OnInitialized()
    {
        RunTimer();
        state = "On Init complete";

        // Uses System.Timers.Timer
        timer.Elapsed += TimeElapsed;
        timer.AutoReset = true;
        timer.Enabled = true;

    }

    private async void TimeElapsed(object? sender, System.Timers.ElapsedEventArgs e)
    {
        // emulate an async data get
        await Task.Delay(100);
        timerMessage = $"Updated at {DateTime.Now.ToLongTimeString()}";
        await InvokeAsync(StateHasChanged);
    } 

    protected async void RunTimer()
    {
        while (await _periodicTimer.WaitForNextTickAsync())
        {
            // emulate an async data get
            await Task.Delay(100);
            message = $"Updated at {DateTime.Now.ToLongTimeString()}";
            await InvokeAsync(StateHasChanged);
        }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            _periodicTimer.Dispose();
            timer.Elapsed -= TimeElapsed;
        }
    }
}
like image 65
MrC aka Shaun Curtis Avatar answered Jan 27 '26 00:01

MrC aka Shaun Curtis



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!