简体   繁体   中英

WPF, how to implement async/await?

I'm learning how to webscrape in WPF. I check the site every 20sec, update my ObservableCollection (myClients) according to search results and display it in Listview (myList). I have 2 Buttons, one to start search and one to stop it.

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). 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.

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.

If you want to do something every x seconds you could use a timer as suggested by 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

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 . 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:

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. 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

DispatcherTimer may be a better solution in this case, like in the below example:

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. 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.

The documentation I link to, as well as this answer , also give details on this.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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