Кажется, что задача не отменяется полностью

У меня есть текстовое поле, которое не позволяет пользователю писать недопустимые символы, которые я указал с помощью регулярного выражения, и если пользователь записывает какой-либо из них, появляется всплывающее окно и остается там в течение 5 секунд, и я установил эту продолжительность, используя Task.Delay(5000, cts.Token) который также имеет токен отмены, и пока всплывающее окно отображается на экране, если пользователь вводит разрешенный символ, всплывающее окно исчезает, и эта задача задержки отменяется и удаляется, но если я напишу недопустимый символ, а затем немедленно напишу разрешенный символ, и если я сделаю это очень быстро, то, когда я напишу недопустимый символ, чтобы открыть всплывающее окно, всплывающее окно исчезнет менее чем за 5 секунд (в случайную секунду, я думаю, это потому, что задача задержки отменяется в этот момент а затем вызывает исчезновение всплывающего окна), что является той же проблемой, что и тогда, когда я не использовал токены отмены для отмены задачи задержки. Но с кодами, которые я написал, я не знаю, как задача не отменяется полностью, а может быть, но проблема в другом ...

    CancellationTokenSource cts;
    protected async override void OnPreviewTextInput(TextCompositionEventArgs e)
    {
        if (txtName.IsFocused)
        {
            if (cts != null && !regex.IsMatch(e.Text))
            {
                cts.Cancel();
                cts.Dispose();
            }

            cts = new CancellationTokenSource();

            if (regex.IsMatch(e.Text))
            {
                e.Handled = true;

                txtName.CaretIndex = txtName.Text.Length;

                if (AlertPopup.IsOpen == false)
                {
                    AlertPopup.IsOpen = true;
                    txtName.Focus();
                    try
                    {
                        await Task.Delay(5000, cts.Token);
                    }
                    catch (TaskCanceledException) { }
                    finally
                    {
                        AlertPopup.IsOpen = false;
                    }
                }
            }
            else
            {
                AlertPopup.IsOpen = false;
            }

            base.OnPreviewTextInput(e);
        }
    }

person SMH    schedule 14.11.2020    source источник


Ответы (1)


Метод OnPreviewTextInput() всегда выполняется только в потоке пользовательского интерфейса. Точно так же продолжение после await в этом методе всегда выполняется только в потоке пользовательского интерфейса. Поток пользовательского интерфейса может делать только одну вещь за раз. Это означает, что в случае, когда всплывающее окно с ошибкой уже отображается, когда есть новый ввод пользователя и код пытается отменить это отображение (то есть путем отмены токена отмены задержки), await, что впоследствии приводит к тому, что свойство AlertPopup.IsOpen получает значение false фактически не выполняет продолжение, пока не вернется новый вызов OnPreviewTextInput().

Итак, когда есть новый пользовательский ввод, этот ввод либо действителен, либо недействителен. Если это действительно так, вы устанавливаете IsOpen на false и возвращаете. Скрыть всплывающее окно позже - не проблема. Но если ввод недействителен, текущее состояние всплывающего окна таково, что IsOpen истинно, вы пропускаете попытку его отображения, метод возвращается, и затем выполняется продолжение отмененной задачи, скрывая всплывающее окно.

Обратите внимание, что важной частью здесь является то, что продолжение происходит после возврата из метода. Даже если вы попытались показать всплывающее окно без каких-либо условий (т.е. не проверяйте IsOpen перед тем, как сделать это), всплывающее окно будет показано, а затем сразу же скрыто.

Вы можете решить эту проблему, используя другой механизм синхронизации (например, тот, в котором вы можете продлить таймер вместо отмены и перезапуска, например _ 11_), или используя механизм, который у вас есть сейчас, но сохраняя счетчик, который увеличивается каждый раз, когда запускается недопустимое состояние, и уменьшается каждый раз, когда ожидание таймера завершается и скрывает всплывающее окно только тогда, когда счетчик снова достигает нуля.

Но на самом деле я думаю, что это не очень хорошее взаимодействие с пользователем. Как пользователь, я действительно возненавидел всплывающие подсказки по времени, потому что они никогда не остаются видимыми достаточно долго. Гораздо более распространенным методом в WPF является использование одного из встроенных механизмов проверки (существует как минимум три различных варианта проверки в WPF, каждый со своими плюсами и минусами), а затем установите обычное значение всплывающей подсказки для элемента в своем сообщении об ошибке, чтобы пользователь мог видеть его, пока он наведен указатель мыши на элементе.

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

person Peter Duniho    schedule 14.11.2020