'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 |