Могу ли я ждать изменения строки? - PullRequest
0 голосов
/ 05 марта 2019

У меня есть приложение UWP, в котором есть PDFReader.У PDFReader есть окно поиска.После ввода слова в текстовое поле поиска вы можете начать поиск.В настоящее время так, что если вы начинаете поиск, вам нужно подождать 3 секунды, пока вы не увидите сообщение о том, как часто встречается слово.

Теперь я хочу заполнить эти 3 секунды сообщением "Поиск ...".

У меня было одно решение:

private async void DoSearch(...)
{
//...
Task<string> searchingText = setSearchMsg();
infoBox.Text = await searchingText;
//...
}

private async Task<string> setSearchMsg()
{
infoBox.Text = "Searching...";
await Task.Delay(1);
return "";
}

Но это выглядит не так.Одна вещь, которую я заметил, - когда я ищу слово и слово может быть найдено 3000 раз на 80 страницах, infoBox пропускает сообщение «Поиск ...» и снова заполняет информационный блок пустотой.Чтобы исправить это, я мог бы изменить Task.Delay на Task.Delay(100), что, как я полагаю, не может быть правильным решением.

Ответы [ 4 ]

0 голосов
/ 05 марта 2019

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

Я предлагаю переместить логику поиска в собственный метод и вызвать ее асинхронно в пуле потоков.Сохраните логику отображения в существующем методе, но используйте Task.Run для выполнения отдельного метода, который выполняет фактический поиск.

private async void DoSearch(...)
{
    infoBox.Text = "Searching....";
    await Task.Run( () => DoActualSearch() );
    infoBox.Text = "";
}
0 голосов
/ 05 марта 2019

Этот фрагмент кода должен работать.Волшебство заключается в изменении текста перед ожиданием.

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

private async void DoSearch(...)
{
//...
infoBox.Text="Searching..."
var result= await SearchTextAsync();
//await Task.Delay(100ms); //add this if your search can perform too quickly to avoid flickering effect
infoBox.Text = result;// or clean
//...
}
0 голосов
/ 05 марта 2019

Вы должны использовать Microsoft Reactive Framework (он же Rx) - NuGet System.Reactive.Windows.Threading и добавить using System.Reactive; using System.Reactive.Linq; - тогда вы можете сделать что-то удивительное.

Я смоделировал немного кода, похожего на ваш.Я начал с TextBox и TextBlock и фиктивного метода для выполнения поиска:

    private async Task<string> PerformSearchAsync(string text)
    {
        await Task.Delay(TimeSpan.FromSeconds(2.0));
        return "Hello " + text;
    }

Теперь в конструкторе после this.InitializeComponent(); я создал следующую наблюдаемую, которая реагирует на всеTextChanged события на моем textBox1:

        IObservable<EventPattern<TextChangedEventArgs>> textChanges =
            Observable
                .FromEventPattern<TextChangedEventHandler, TextChangedEventArgs>(
                    h => textBox1.TextChanged += h,
                    h => textBox1.TextChanged -= h);

Затем я написал запрос для ответа на изменения текста:

        IObservable<string> query =
            textChanges
                .Select(x =>
                    Observable
                        .FromAsync(() => PerformSearchAsync(textBox1.Text))
                        .StartWith("Searching..."))
                .Switch();

По сути, это ожидание каждого изменения текста и инициацияасинхронный вызов PerformSearchAsync, который возвращает результаты поиска в виде строки, а также сразу возвращает строку "Searching..."..Switch() используется, чтобы убедиться, что только последний вызов PerformSearchAsync действительно возвращает результат.

Теперь я могу просто наблюдать запрос:

        _subscription =
            query
                .ObserveOnDispatcher()
                .Subscribe(x => textBlock1.Text = x);

Переменная _subscriptionопределяется как private IDisposable _subscription = null;.Он используется для того, чтобы я мог позвонить _subscription.Dispose(), чтобы безопасно закрыть подписку.

Он ведет себя так, как вы хотите.

Весь мой код выглядит так:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reactive;
using System.Reactive.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;

namespace App5
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainPage : Page
    {
        private IDisposable _subscription = null;

        public MainPage()
        {
            this.InitializeComponent();

            IObservable<EventPattern<TextChangedEventArgs>> textChanges =
                Observable
                    .FromEventPattern<TextChangedEventHandler, TextChangedEventArgs>(
                        h => textBox1.TextChanged += h,
                        h => textBox1.TextChanged -= h);

            IObservable<string> query =
                textChanges
                    .Select(x =>
                        Observable
                            .FromAsync(() => PerformSearchAsync(textBox1.Text))
                            .StartWith("Searching..."))
                    .Switch();

            _subscription =
                query
                    .ObserveOnDispatcher()
                    .Subscribe(x => textBlock1.Text = x);
        }

        private async Task<string> PerformSearchAsync(string text)
        {
            await Task.Delay(TimeSpan.FromSeconds(2.0));
            return "Hello " + text;
        }
    }
}

Вы должны быть в состоянии приспособить это для работы с вашим кодом.

0 голосов
/ 05 марта 2019

Установка сообщения «Поиск ...» не асинхронная часть вашего кода - это мгновенно.Это поиск, который на самом деле занимает время, верно?Но то, что вы делаете, это устанавливаете сообщение ... ожидаете ... и немедленно очищаете сообщение.Вам нужно установить сообщение, выполнить медленную операцию, а затем очистить сообщение.

Это должно быть примерно так:

private async void DoSearch(...)
{
   // Before starting the heavy-duty async operation.
   infoBox.Text = "Searching...";

   // Do the search. Infobox should display "Searching..." throughout.
   await pdfReader.SearchAndHighlightWords(searchString);

   // Now we're back from the slow async operation. Clear the infobox.
   infoBox.Text = "";
}
...