У меня есть простое веб-приложение Blazor (серверная часть, .NET 8, VS2022). Здесь главная страница:
@page "/"
@using System.Timers
@rendermode InteractiveServer
<p>@StatusMessage</p>
<button class = "btn btn-primary" @onclick = "StartHandler">Start</button>
@code {
private void StartHandler()
{
Countdown = 100;
timerCounter.Interval = 1000;
timerCounter.Elapsed += TimerCounter_Elapsed;
timerCounter.AutoReset = true;
timerCounter.Start();
}
private void TimerCounter_Elapsed(object? sender, ElapsedEventArgs e)
{
Countdown--;
StatusMessage = $"{Countdown}";
}
private Timer timerCounter = new Timer();
private int Countdown = 0;
private string StatusMessage = "";
}
Он должен запускать таймер при нажатии кнопки и показывать обратный отсчет на странице. Но когда я нажимаю кнопку, ничего не происходит. При следующих кликах отображается количество, например, 99 или 97. Я не понимаю, что происходит. Почему событие таймера не срабатывает? Спасибо.
Я попробовал обратный отсчет при нажатии кнопки, и он работает. Но мне нужно событие срабатывания таймера.
Вот мой файл App.razor:
<!DOCTYPE html>
<html lang = "en">
<head>
<meta charset = "utf-8" />
<meta name = "viewport" content = "width=device-width, initial-scale=1.0" />
<base href = "/" />
<link rel = "stylesheet" href = "bootstrap/bootstrap.min.css" />
<link rel = "stylesheet" href = "app.css" />
<link rel = "stylesheet" href = "BlazorApp.styles.css" />
<link rel = "icon" type = "image/png" href = "favicon.png" />
<HeadOutlet />
</head>
<body>
<Routes />
<script src = "_framework/blazor.web.js"></script>
</body>
</html>
Добавьте StateHasChanged
. Из документации: «Уведомляет компонент об изменении его состояния. Если применимо, это приведет к повторной визуализации компонента».
private void TimerCounter_Elapsed(object? sender, ElapsedEventArgs e)
{
Countdown--;
StatusMessage = $"{Countdown}";
StateHasChanged();
}
Я только что протестировал его здесь, и он отлично работает: blazorfiddle.com
Я поставил его в ожидании InvokeAsync(StateHasChanged), и теперь он работает.
Как указано в другом ответе, решение состоит в том, чтобы добавить вызов StateHasChanged
к методу обратного вызова.
Причина в том, что обратные вызовы не являются событиями пользовательского интерфейса. Автоматического вызова StateHasChanged
не существует, поэтому вам придется добавить его вручную.
StateHasChanged
должен выполняться в потоке пользовательского интерфейса [контекст синхронизации].
Процесс таймера [выполняющийся в собственном потоке] планирует TimerCounter_Elapsed
запуск в пуле потоков. Вызов StateHasChanged
через метод InvokeAsync
компонента переключает выполнение на поток пользовательского интерфейса.
private void TimerCounter_Elapsed(object? sender, ElapsedEventArgs e)
{
Countdown--;
StatusMessage = $"{Countdown}";
InvokeAsync(StateHasChanged);
}
Я получил InvalidOperationException: текущий поток не связан с диспетчером. Используйте InvokeAsync(), чтобы переключить выполнение на Dispatcher при запуске рендеринга или состояния компонента.