EventCallback выдает «Текущий поток не связан с диспетчером»

Я пытаюсь создать простой элемент управления поисковым вводом с функцией устранения дребезга на основе таймера.

Search.razor:

<input type = "search" placeholder = "Search" @oninput=@((e) => SearchChanged(e)) />

@code {
    
  [Parameter]
  public EventCallback<string> OnSearchChanged { get; set; }

  private System.Timers.Timer? _debounceTimer;

  string Text = string.Empty;

  public void SearchChanged(ChangeEventArgs e)
  {
     Text = e.Value == null ? string.Empty : e.Value.ToString()!;
     // Debounce search
     _debounceTimer?.Stop();
     _debounceTimer?.Dispose();
     _debounceTimer = new System.Timers.Timer(200);
     _debounceTimer.Elapsed += DebounceTimerElapsed;
     _debounceTimer.Enabled = true;
     _debounceTimer.Start();
  }

  private void DebounceTimerElapsed(object? sender, EventArgs e)
  {
    _debounceTimer?.Dispose();
    OnSearchChanged.InvokeAsync(Text);    // <<< Dispatcher exception
  }
}

Я думаю, что имеет смысл включить в этот компонент функцию Debounce, чтобы мне не приходилось повторять ее для каждого родителя. Но я не могу использовать EventCallback, чтобы сообщить родительскому компоненту, что таймер истек. Это вызывает печально известное исключение The current thread is not associated with the Dispatcher.

На этой странице Университета Блазор объясняется, что события, не связанные с пользовательским интерфейсом, такие как Timer.Elapsed, появляются в другом потоке. Таймер действительно виноват, потому что, очевидно, он работает, если я звоню OnSearchChanged.InvokeAsync() прямо из SearchChanged. Но в этом случае InvokeAsync(), похоже, не синхронизируется с потоком пользовательского интерфейса. Тем не менее, я думаю, что должен быть простой способ заставить эту работу работать.

Есть ли (простой) способ сообщить родительскому компоненту, что таймер в дочернем компоненте истек?

К вашему сведению — см. эти реализации отскока codeproject.com/Articles/5308567/An-Easier-Blazor-Debounce и shauncurtis.github.io/Building-Blazor-Applications/…

MrC aka Shaun Curtis 04.04.2024 23:56

Спасибо, @MrCakaShaunCurtis. На самом деле я уже нашел первую статью в блоге Джереми Ликнесса.

flip 05.04.2024 01:00

Проблема в отделении средства устранения дребезга от логики поиска, которая, вероятно, является асинхронной и представляет собой своего рода удаленный вызов, заключается в том, что вы все равно можете получить состояние гонки, когда поиск выполняется медленнее, чем период устранения дребезга. Если вы их плотно свяжете, новый поиск будет выполняться только после завершения предыдущего.

MrC aka Shaun Curtis 05.04.2024 11:19
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
3
78
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

Сообщение об ошибке, с которым вы сталкиваетесь, часто встречается при попытке обновить пользовательский интерфейс из потока, не связанного с пользовательским интерфейсом, в Blazor. Такая ситуация часто возникает в обработчиках событий асинхронных операций или таймерах, как в вашем случае с System.Timers.Timer. Поскольку событие Elapsed таймера выполняется в потоке пула потоков, а не в потоке Blazor Dispatcher, попытка изменить состояние компонента напрямую приводит к этой ошибке.

Чтобы это исправить, необходимо убедиться, что логика обновления пользовательского интерфейса выполняется в потоке диспетчера Blazor. Этого можно добиться, используя метод InvokeAsync для обратного вызова в контекст компонента Blazor. Вот как вы можете изменить свой метод поиска для использования InvokeAsync:

(Предполагая, что вы будете повторно использовать этот компонент и захотите @bind использовать его.

DebouncedSearch.razor


@using System.Timers
<input @bind = "SearchText"
       @bind:event = "oninput"
       autocomplete = "off"
       type = "text" />

@code {

    [Parameter] public string? Value { get; set; }
    [Parameter] public EventCallback<string> ValueChanged { get; set; }

    private Timer _debounceTimer = new();
    private string _searchText = string.Empty;

    private string SearchText
    {
        get => _searchText;
        set
        {
            _searchText = value;
            _debounceTimer.Stop();
            _debounceTimer.Start();
        }
    }

    protected override void OnInitialized()
    {
        _debounceTimer.Interval = 200;
        _debounceTimer.AutoReset = false;
        _debounceTimer.Elapsed += Search;
    }

    private async void Search(object? source, ElapsedEventArgs e)
    {
        await InvokeAsync(async () =>
        {
            await ValueChanged.InvokeAsync(_searchText);
        });
    }
}

Index.razor или любой другой родительский элемент

<DebouncedSearch @bind-Value = "@searchText" />

<label>Text: @searchText</label>

@code {
    private string? searchText;
}

Для тех, кто читает это: ответ на ОП заключается в том, как вызывается InvokeAsync; await InvokeAsync(async () = { await ValueChanged.InvokeAsync(_searchText); }); Привязка события oninput с использованием метода получения/установки, как в этом ответе, или с использованием пустоты, как в исходном вопросе, является вопросом личных предпочтений.

flip 04.04.2024 15:24

Ваш входной код не скомпилируется — например, input @bind-value = "SearchText" должно быть input @bind = "SearchText". То же самое с @bind-value:event = "oninput"

MrC aka Shaun Curtis 05.04.2024 11:43

Совершенно никаких проблем.

MrC aka Shaun Curtis 05.04.2024 17:12

Вот немного другая версия ответа @Verbe:

  1. использует bind:after для управления дебаунсером и передачи обновлений родителю.
  2. Использует легкий System.Threading.Timer.
@using System.Threading

<input @bind = "_searchText"
       @bind:event = "oninput"
       @bind:after = "Debounce"
       autocomplete = "off"
       type = "text" />

@code {
    [Parameter] public string? Value { get; set; }
    [Parameter] public EventCallback<string?> ValueChanged { get; set; }

    private System.Threading.Timer? _timer;
    private string? _searchText;
    private int _debounceMilliseconds = 300;

    protected override void OnParametersSet()
    {
        this._searchText = this.Value;
    }

    private void Debounce()
    {
        _timer?.Dispose();
        _timer = new Timer(this.OnTimerExpired, null, _debounceMilliseconds, -1);
    }

    private void OnTimerExpired(object? state)
    {
        _timer?.Dispose();
        _timer = null;
        // invoke OnValueChanged on the Synchronisation Context
        this.InvokeAsync(OnValueChanged);
    }

    private Task OnValueChanged()
        => this.ValueChanged.InvokeAsync(_searchText);
}

Есть проблема в том, чтобы сделать это таким образом. Вы отделяете дебаунсер от логики поиска. Если эта логика представляет собой асинхронный вызов удаленной службы, вы все равно можете получить состояние гонки, когда поиск будет медленнее, чем ваш период устранения дребезга.

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

Похожие вопросы

Связь между двумя приложениями, размещенными в IIS на одном хосте – соединение не удалось установить
Интеграция AAD B2C с Entra ID
Blazor: отображение результатов выполнения команды по мере их получения
Как я могу отладить код Blazor рендеринга на стороне сервера в компоненте?
Метод Blazorise SelectedTabChanged вызывается дважды, поэтому в конечном итоге выбирается неправильная вкладка
Blazor (InteractiveServer) + библиотека удостоверений ASP.NET и GDPR
Как создать/определить конкретный дизайн или макет телефона для серверного приложения Blazor
Почему мне нужно вызывать StateHasChanged(), когда я делаю дочерний компонент видимым?
Проблема с отображением сообщения Snackbar после успешного обновления пароля в Blazor с помощью MudBlazor
В серверном приложении Blazor у меня есть бесконечный цикл while для получения данных из темы Kafka на моей странице razor.cs. Как я могу предотвратить блокировку пользовательского интерфейса?