简体   繁体   English

WPF,如何实现异步/等待?

[英]WPF, how to implement async/await?

I'm learning how to webscrape in WPF. 我正在学习如何在WPF中进行网络抓取。 I check the site every 20sec, update my ObservableCollection (myClients) according to search results and display it in Listview (myList). 我每20秒检查一次网站,根据搜索结果更新我的ObservableCollection(myClients),并将其显示在Listview(myList)中。 I have 2 Buttons, one to start search and one to stop it. 我有2个按钮,一个开始搜索,一个停止搜索。

I didn't know how to implement button autoclick every X sec (which would solve all my problems, am i right?) so i had to use Task.Delay(20000). 我不知道如何每X秒实现一次按钮自动点击(这将解决我的所有问题,对吗?),所以我不得不使用Task.Delay(20000)。 Program works, it doesn't freeze right at the start like if i had used Thread.Sleep(), but if i press the Stop button and then Start, everything freezes. 程序正常工作,它不会像我曾经使用过Thread.Sleep()那样一开始就冻结,但是如果我先按“停止”按钮,然后再按“开始”,则一切都会冻结。

I will upload only portion of the code that seems to be the problem. 我将仅上载似乎是问题的部分代码。 Note that the whole program at the moment is mostly reverse-engineered from several different programs as i am still a beginner. 请注意,由于我仍然是初学者,因此目前整个程序大部分都是从几个不同的程序反向工程而来的。

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

Running a while loop on the UI thread may freeze the application as the UI thread cannot both process UI events and execute a loop or doing anything else simultaneously. 在UI线程上运行while循环可能会冻结应用程序,因为UI线程无法同时处理UI事件和执行循环或同时执行其他任何操作。

If you want to do something every x seconds you could use a timer as suggested by EJoshuaS. 如果您想每隔x​​秒钟执行一次操作,则可以使用EJoshuaS建议的计时器。 There is a DispatcherTimer class in WPF that fires a Tick event on the UI thread at an interval specified by the Interval property: https://msdn.microsoft.com/en-us/library/system.windows.threading.dispatchertimer%28v=vs.110%29.aspx WPF中有一个DispatcherTimer类,它以Interval属性指定的时间间隔在UI线程上触发Tick事件: https : //msdn.microsoft.com/zh-cn/library/system.windows.threading.dispatchertimer%28v = vs.110%29.aspx

You don't want to perform the GET request to the web server on the UI thread though so you should probably use a System.Timer.Timer: https://msdn.microsoft.com/en-us/library/system.timers.timer(v=vs.110).aspx . 但是,您不想在UI线程上对Web服务器执行GET请求,因此您可能应该使用System.Timer.Timer: https : //msdn.microsoft.com/zh-cn/library/system.timers .timer(v = vs.110).aspx This is a different type of timer that runs on a background thread. 这是在后台线程上运行的另一种计时器。

Since you can only access UI controls such as TextBlocks and ListBoxes on the thread on which they were originally created - that is the UI thread - you will have to use the dispatcher to marshall any code that access these controls back to the UI thread in your Elapsed event handler: 由于您只能在最初创建它们的线程(即UI线程)上访问UI控件(例如TextBlocks和ListBoxes),因此您将不得不使用分派器将访问这些控件的所有代码编组回您的UI线程中。过去的事件处理程序:

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..."; }));
}

The golden rule to maintain a responsive application is to execute any long-running code on a background thread but you must only access UI controls back on the UI thread. 维护响应式应用程序的黄金法则是在后台线程上执行所有长时间运行的代码,但是您必须仅在UI线程上重新访问UI控件。 Please refer to MSDN for more information about the threading model in WPF: https://msdn.microsoft.com/en-us/library/ms741870(v=vs.110).aspx 有关WPF中的线程模型的更多信息,请参考MSDN: https : //msdn.microsoft.com/en-us/library/ms741870(v= vs.110) .aspx

DispatcherTimer may be a better solution in this case, like in the below example: 在这种情况下, 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");
    }
}

Basically, this will raise an event at a given interval. 基本上,这将在给定的时间间隔引发一个事件。 Note that Windows Forms also has a timer , as does System.Threading , but you want to make sure you use DispatcherTimer rather than those. 请注意,Windows Forms和System.Threading一样还有一个计时器 ,但是您要确保使用DispatcherTimer而不是它们。 In particular, the one from System.Threading tends not to mix well with UIs because it runs its actions on the thread pool and WPF in particular is very fussy about how you update your UI from background threads. 特别是,System.Threading中的一个往往不能与UI很好地混合使用,因为它在线程池上运行其动作,尤其是WPF对于如何从后台线程更新UI非常挑剔。

The documentation I link to, as well as this answer , also give details on this. 我链接到的文档以及此答案也提供了详细信息。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM