我正在学习如何在WPF中进行webscrape。我每20秒检查一次网站,根据搜索结果更新我的ObservableCollection(myClients)并将其显示在Listview(myList)中。我有2个按钮,一个用于开始搜索,另一个用于停止搜索。
我不知道如何每X秒实现按钮自动切换(这将解决我的所有问题,我是对的吗?)所以我不得不使用Task.Delay(20000)。程序有效,它不会在开始时冻结,就像我使用了Thread.Sleep(),但是如果我按下Stop按钮然后开始,一切都会冻结。
我只会上传部分似乎是问题的代码。请注意,目前整个程序主要是从几个不同的程序进行逆向工程,因为我还是初学者。
private async void Button_Click(object sender, RoutedEventArgs e) //Start button
{
string car;
string price;
string link;
wantToAbort = false;
while (!wantToAbort)
{
// ----Simulate GET request----
//-----End GET----
myList.ItemsSource = myClients;
string searchCar = txtBlock.Text + " " + txtBlock2.Text;
var articleNodes = htmlDoc.DocumentNode.SelectNodes($"//*[@id='main_content']/div[1]/div[2]/ul[1]//*[text()[contains(., '{searchCar}')]]");
if (articleNodes != null && articleNodes.Any())
{
foreach (var articleNode in articleNodes)
{
car = WebUtility.HtmlDecode(articleNode.InnerText);
price = WebUtility.HtmlDecode(articleNode.ParentNode.ParentNode.SelectSingleNode("span").InnerText);
link = WebUtility.HtmlDecode(articleNode.ParentNode.ParentNode.Attributes["href"].Value);
var tempUser = new User(car, price, link);
if (!myClients.Any(x=>x.Link == tempUser.Link))
{
myClients.Insert(0, tempUser); //Inserts new item if Links are different
txtBlock3.Text = "Searching...";
}
}
await Task.Delay(20000); //This seems to be an issue
}
}
}
private void Button_Click_1(object sender, RoutedEventArgs e) //Stop button
{
wantToAbort = true;
txtBlock3.Text = "Ready to search again!";
}
答案 0 :(得分:1)
在UI线程上运行while循环可能会冻结应用程序,因为UI线程既不能处理UI事件,也不能同时执行循环或执行任何其他操作。
如果你想每x秒做一些事情,你可以使用EJoshuaS建议的计时器。 WPF中有一个DispatcherTimer类,它按照Interval属性指定的间隔在UI线程上触发Tick事件:https://msdn.microsoft.com/en-us/library/system.windows.threading.dispatchertimer%28v=vs.110%29.aspx
您不希望在UI线程上对Web服务器执行GET请求,因此您应该使用System.Timer.Timer:https://msdn.microsoft.com/en-us/library/system.timers.timer(v=vs.110).aspx。这是一种在后台线程上运行的不同类型的计时器。
由于您只能在最初创建它们的线程(即UI线程)上访问UI控件(如TextBlocks和ListBoxes),因此您必须使用调度程序将访问这些控件的任何代码编组回UI你的Elapsed事件处理程序中的线程:
private static void OnTimedEvent(Object source, ElapsedEventArgs e)
{
//call the web server here....
//dispatch any access to any UI control
txtBlock3.Dispatcher.Invoke(new Action(() = > { txtBlock3.Text = "Searching..."; }));
}
维护响应式应用程序的黄金法则是在后台线程上执行任何长时间运行的代码,但您必须只能在UI线程上访问UI控件。有关WPF中的线程模型的更多信息,请参阅MSDN:https://msdn.microsoft.com/en-us/library/ms741870(v=vs.110).aspx
答案 1 :(得分:0)
DispatcherTimer可能是更好的解决方案,如下例所示:
public partial class MainWindow : Window
{
private DispatcherTimer timer;
public MainWindow()
{
InitializeComponent();
timer = new DispatcherTimer();
timer.Interval = new TimeSpan(0, 0, 220);
timer.Tick += Timer_Tick;
timer.Start();
}
private void Timer_Tick(object sender, EventArgs e)
{
// Do something on your UI
Trace.TraceInformation("Timer expired");
}
}
基本上,这会以给定的间隔引发事件。请注意Windows窗体also has a timer和System.Threading一样,但您要确保使用DispatcherTimer
而不是{{1}}。特别是,来自System.Threading的那个往往不能与UI很好地混合,因为它在线程池上运行它的动作,特别是WPF对于如何从后台线程更新UI非常挑剔。
我链接到的文档以及this answer也提供了详细信息。