Таймер не работает в веб-приложении Blazor

У меня есть простое веб-приложение 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>
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
61
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Добавьте StateHasChanged. Из документации: «Уведомляет компонент об изменении его состояния. Если применимо, это приведет к повторной визуализации компонента».

private void TimerCounter_Elapsed(object? sender, ElapsedEventArgs e)
{        
    Countdown--;
    StatusMessage = $"{Countdown}";
    StateHasChanged();        
}

Я получил InvalidOperationException: текущий поток не связан с диспетчером. Используйте InvokeAsync(), чтобы переключить выполнение на Dispatcher при запуске рендеринга или состояния компонента.

max 30.06.2024 15:19

Я только что протестировал его здесь, и он отлично работает: blazorfiddle.com

D A 30.06.2024 15:24

Я поставил его в ожидании InvokeAsync(StateHasChanged), и теперь он работает.

max 30.06.2024 15:25

Как указано в другом ответе, решение состоит в том, чтобы добавить вызов StateHasChanged к методу обратного вызова.

Причина в том, что обратные вызовы не являются событиями пользовательского интерфейса. Автоматического вызова StateHasChanged не существует, поэтому вам придется добавить его вручную.

StateHasChanged должен выполняться в потоке пользовательского интерфейса [контекст синхронизации].

Процесс таймера [выполняющийся в собственном потоке] планирует TimerCounter_Elapsed запуск в пуле потоков. Вызов StateHasChanged через метод InvokeAsync компонента переключает выполнение на поток пользовательского интерфейса.

private void TimerCounter_Elapsed(object? sender, ElapsedEventArgs e)
{        
    Countdown--;
    StatusMessage = $"{Countdown}";
    InvokeAsync(StateHasChanged);        
}

Другие вопросы по теме