'Blazor Timer call async API task to update UI

I am setting up a timer in a Blazor server-side page. The goal is to call an API every x seconds and based on the return value, update the UI.

I got this code:

private string Time { get; set; }

protected override void OnInitialized()
{
    var timer = new System.Threading.Timer((_) =>
    {
        Time = DateTime.Now.ToString();
        InvokeAsync(() =>
        {
            StateHasChanged();
        });
    }, null, 0, 1000);
    base.OnInitialized();
}

This works beautifully. The UI was updated every second with the new time value. However, I can't figure out how to call an async task to get the value. I would like to replace the line:

Time = DateTime.Now.ToString();

with a line that calls the following function:

private async Task<string> GetValue()
{
    var result = await _api.GetAsync<StringDto>("/api/GetValue");
    return result.Text;
}

I've tried this line:

Time = GetValue().Result;

But I received this following error:

The current thread is not associated with the Dispatcher. Use InvokeAsync() to switch execution to the Dispatcher when triggering rendering or component state.

What do I need to do to call the async method?

Thanks a bunch!



Solution 1:[1]

Try this code:

  private string Time { get; set; }

protected override void OnInitialized()
{
    base.OnInitialized();
    var timer = new System.Threading.Timer((_) =>
    {

        InvokeAsync( async ()  =>
        {
            Time = await GetValue();
            StateHasChanged();
        });
    }, null, 0, 1000);

}

Your GetValue method should be:

private async Task<string> GetValue()
{
    return await _api.GetAsync<StringDto>("/api/GetValue");
  
}

Solution 2:[2]

You probably don't want to Invoke() the GetValue(), that would be rather pointless. You can implement the timer like this:

System.Threading.Timer timer;
protected override void OnInitialized()
{
    timer = new System.Threading.Timer(async _ =>  // async void
    {
        Time = await GetValue();
        // we need StateHasChanged() because this is an async void handler
        // we need to Invoke it because we could be on the wrong Thread          
        await InvokeAsync(StateHasChanged);
    }, null, 0, 1000);
}

I used a field to store the Timer because you should dispose it, add this to the Razor section:

@implements IDisposable

and this to the code:

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

Solution 3:[3]

In .NET 6 You can use PeriodicTimer class as follows

private string Time { get; set; }

     protected override async void OnAfterRender(bool firstRender)
{
    if (firstRender)
    {
        using var periodicTimer = new PeriodicTimer(TimeSpan.FromMinutes(10));
        while (await periodicTimer.WaitForNextTickAsync())
        {
          Time=   await GetValue();

            await InvokeAsync(StateHasChanged);
        }
    }
}
     

Running it without tieing up a LifeCycle method forever:

@implements IDisposable
PeriodicTimer periodicTimer = new (TimeSpan.FromMinutes(10);

protected override void OnInitialized()
{
    RunTimer();  // fire-and-forget
}

async void RunTimer()
{
    while (await periodicTimer.WaitForNextTickAsync()) { .... }    
}

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

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Henk Holterman
Solution 2
Solution 3 Henk Holterman