正确使用等待和异步

时间:2020-01-11 23:22:28

标签: c# async-await

我不太确定自己在这里做的是正确的,因为我没有使用太多async / await方法,而是打算在一个小型应用程序中学习更多。

代码:

        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());
        }

我要做的就是单击一个按钮并导入URL列表,检查HTML中的字符串,然后报告是或否。

目标是在不锁定UI的情况下运行应用程序,我可以使用后台工作程序,但是如果按原样运行代码,则会出现错误:

跨线程操作无效:控制'comboBoxPlatform'从创建该线程的线程之外的其他线程访问。

我可以通过调用哪些方法绕过,我是否走在正确的轨道上?

任何帮助将不胜感激。

2 个答案:

答案 0 :(得分:2)

正如您所说,您需要从UI线程中填充comboBox。从另一个线程访问它的任何尝试都会给您CrossThreadException。我发现执行此操作的最简单方法是像这样从Task返回信息:

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

    }

因为能够捕获异常非常重要,所以您可以知道正在运行的独立任务是否失败以及失败的原因。

与上述代码相同,但具有一些简单的异常处理。

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

您可能想做的另一件事是向用户提供进度反馈。为此,您提到了Invoke-显然,这是旧方法。来自许多地方的建议似乎是使用IProgress。

这里有一些简单的更改,随着CPU绑定任务的进行,这些更改将近乎实时的结果反馈给用户。

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

    }

答案 1 :(得分:-2)

由于错误状态,您正在创建的新线程无法访问ComboBox,因为它没有在该新线程中实例化。虽然您有异步等待的正确想法。

我认为(这只是一种实现方式),您最好的选择是将File.ReadAllText(@"Platforms\" + comboBoxPlatform.Text).Split('|');作为参数传递,这样就不需要在新版本中访问ComboBox线程。

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