Using Await & Async Properly

我只是一个虾纸丫 提交于 2020-07-22 08:35:33

问题


I'm not entirely sure what I am doing here is correct as I haven't used the async / await methods much and was going to learn more in a small application.

Code:

        public async Task ImportURLs() {

            // read contents of the chosen combobox platform ...
            var comp = File.ReadAllText(@"Platforms\" + comboBoxPlatform.Text).Split('|');
            var reg = comp[0];
            var fl1 = comp[1];
            var fl2 = comp[2];

            string line;
            OpenFileDialog ofd = new OpenFileDialog
            {
                Filter = "URLs.txt|*.txt"
            };
            if (ofd.ShowDialog() == DialogResult.OK)
            {
                if (ofd.FileName.Trim() != string.Empty)
                {
                    using (StreamReader r = new StreamReader(ofd.FileName))
                    {
                        while ((line = r.ReadLine()) != null)
                        {
                            // check fl1 exists in the url first ...
                            var check_1 = Helpers.FindNeedleInHtml(line, fl1);

                            // if it does the root the url and check the reg page ...
                            if (check_1 == "Yes")
                            {
                                var check_2 = Helpers.FindNeedleInHtml(line, fl2);
                                // check_ & check_2 is "Yes" or "No"
                                AddToListView(Helpers.GetRootUrl(line) + reg, check_1, check_2);
                            }

                        }
                    }
                }
            }
        }

        private async void BtnImportURLs_Click(object sender, EventArgs e)
        {
            await Task.Run(() => ImportURLs());
        }

All I'm doing is clicking a button and importing a list of URLs, checking a string in the HTML then reporting back a Yes or No.

The goal was to run the application without locking the UI up and I could use a background worker, but if I run the code as it is I get the error:

Cross-thread operation not valid: Control 'comboBoxPlatform' accessed from a thread other than the thread it was created on.

Which I could bypass by Invoking, am I on the right track?

Any help would be appreciated.


回答1:


As you say, you need to populate the comboBox from the UI thread. Any attempt to access it from another thread will give you the CrossThreadException. The easiest way I have found to do this is to return the information from the Task like this:

    private async Task<List<string>> GetInformationAsync()
    {
        var returnList = new List<string>();
        Stopwatch sw = new Stopwatch();

        // The UI thread will free up at this point, no "real" work has
        // started so it won;t have hung
        await Task.Run(() =>
        {
            for (var i = 0; i < 10; i++)
            {
                returnList.Add($"Item# {i}");

                // Simulate 10 seconds of CPU load on a worker thread
                sw.Restart();
                while (sw.Elapsed < TimeSpan.FromSeconds(2))
                    ; /* WARNING 100% CPU for this worker thread for 2 seconds */
            }
        });

        // Task that was running on the Worker Thread has completed
        // we return the List<string>

        return returnList;
    }

    private async void button1_Click(object sender, EventArgs e)
    {
        // Get some information and put this into the listBox

        var t = await GetInformationAsync();

        // The CPU intensive task has completed we now have a list of items
        // This will run on the UI thread, as evidenced by no Cross Thread exception
        foreach (string s in t)
            listBox1.Items.Add(s);

    }

And because it's important to be able to catch exceptions so you know if the independent task that was running failed and why it failed.

Same code as above but with some simple exception handling.

    private async Task<List<string>> GetInformationAsync()
    {
        var returnList = new List<string>();
        Stopwatch sw = new Stopwatch();

        // The UI thread will free up at this point, no "real" work has
        // started so it won;t have hung
        await Task.Run(() =>
        {

            for (var i = 0; i < 10; i++)
            {
                returnList.Add($"Item# {i}");

                // Simulate 10 seconds of CPU load on a worker thread
                sw.Restart();
                while (sw.Elapsed < TimeSpan.FromSeconds(2))
                    ; /* WARNING 100% CPU for this worker thread for 2 seconds */
            }

            // Lets pretend that something went wrong up above..
            throw new ArgumentNullException("Lets use this exception");

        });

        // Task that was running on the Worker Thread has completed
        // we return the List<string>

        return returnList;
    }

    private async void button1_Click(object sender, EventArgs e)
    {
        // What if something went wrong we want to catch the exception...
        // the previous verion doesn;t let us do that...

        try
        {
            var t = await GetInformationAsync();

            // No exception was thrown
            foreach (string s in t)
                listBox1.Items.Add(s);
        }
        catch
        {
            listBox1.Items.Clear();
            listBox1.Items.Add("Something went wrong!");
        }
    }

The other thing you might like to be able to do, is to provide feedback of progress to the user. For that you mentioned Invoke - apparently that's the old way of doing it. The suggestion from a number of places seems to be to use an IProgress.

Here are some simple changes that feed near realtime results back to the user as the CPU bound Task progresses.

    private async Task<List<string>> GetInformationAsync(IProgress<int> progress)
    {
        var returnList = new List<string>();
        Stopwatch sw = new Stopwatch();

        // The UI thread will free up at this point, no "real" work has
        // started so it won;t have hung
        await Task.Run(() =>
        {

            for (var i = 0; i < 10; i++)
            {
                // Simulate 10 seconds of CPU load on a worker thread
                sw.Restart();
                while (sw.Elapsed < TimeSpan.FromSeconds(2))
                    ; /* WARNING 100% CPU for this worker thread for 2 seconds */

                returnList.Add($"Item# {i}");

                // Report back to the UI thread
                // increases the progress bar...
                progress.Report((i+1)*10);
            }
        });

        // Task that was running on the Worker Thread has completed
        // we return the List<string>

        return returnList;
    }

    private async void button1_Click(object sender, EventArgs e)
    {

        button1.Enabled = false;

        try
        {
            var progress = new Progress<int>(i => progressBar1.Value = i);

            var t = await GetInformationAsync(progress);

            // No exeception was thrown
            foreach (string s in t)
                listBox1.Items.Add(s);
        }
        catch
        {
            listBox1.Items.Clear();
            listBox1.Items.Add("Something went wrong!");
        }
        finally
        {
            button1.Enabled = true;
        }

    }



回答2:


As the error states, the new thread you're creating cannot access the ComboBox because it is not instantiated in that new thread. You have the right idea with async await though.

I think (and this is just one way to do it) you're best bet is to pass in File.ReadAllText(@"Platforms\" + comboBoxPlatform.Text).Split('|'); as a parameter so that the ComboBox does not need to be accessed in the new thread.

private async void BtnImportURLs_Click(object sender, EventArgs e)
{
    string input = @"Platforms\" + comboBoxPlatform.Text).Split('|');
    await Task.Run(() => ImportURLs(input));
}


来源:https://stackoverflow.com/questions/59699555/using-await-async-properly

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!