我有一个带有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)
,这不是我认为正确的解决方案。
答案 0 :(得分:5)
您应该使用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;
}
}
}
您应该能够对此进行修改以使其与您的代码一起使用。
答案 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
//...
}