我可以等待字符串更改吗?

时间:2019-03-05 08:55:10

标签: c# asynchronous uwp async-await task

我有一个带有PDFReader的UWP应用。 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 "";
}

但是这看起来并不正确。我注意到的一件事是,当我正在寻找一个单词并且可以在80个页面中找到3000个单词时,infoBox会跳过“正在搜索...”消息,并再次将信息框填充为空。 要解决此问题,我可以将Task.Delay更改为Task.Delay(100),这不是我认为正确的解决方案。

4 个答案:

答案 0 :(得分:5)

您应该使用Microsoft的Reactive Framework(又名Rx)-NuGet System.Reactive.Windows.Threading并添加using System.Reactive; using System.Reactive.Linq;-然后您可以做一些令人惊奇的事情。

我模拟了一些类似于您的代码。我从TextBoxTextBlock以及执行搜索的虚拟方法开始:

    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;
        }
    }
}

您应该能够对此进行修改以使其与您的代码一起使用。

答案 1 :(得分:2)

听起来像您的搜索逻辑正在阻塞主线程,并且不包含任何await语句,因此从不产生控制权。这意味着处理标签重画的消息循环没有机会连续运行,因此显示结果不一致。

我建议将搜索逻辑移到其自己的方法中,并在线程池上异步调用它。将显示逻辑保留在您现有的方法中,但是使用Task.Run执行执行实际搜索的单独方法。

private async void DoSearch(...)
{
    infoBox.Text = "Searching....";
    await Task.Run( () => DoActualSearch() );
    infoBox.Text = "";
}

答案 2 :(得分:1)

设置“正在搜索...”消息不是代码的异步部分-它是瞬时的。搜索实际上需要时间,对吗?但是您正在做的是设置消息...等待...,然后立即清除消息。您需要设置消息,执行慢速操作,然后清除消息。

应该是这样的:

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 = "";
}

答案 3 :(得分:1)

此代码段应该有效。魔术是在等待之前更改您的文本。

如果搜索执行得太快并且文本也快速变化,则可以为每次搜索添加一个小的延迟,例如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
//...
}