Правильное использование Await & Asyn c - PullRequest
1 голос
/ 12 января 2020

Я не совсем уверен, что то, что я делаю здесь, правильно, поскольку я не очень много использовал методы asyn c / 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, затем сообщаем да или нет.

Цель была чтобы запустить приложение без блокировки пользовательского интерфейса, и я мог бы использовать фоновый рабочий, но если я запускаю код, как он есть, я получаю ошибку:

Межпотоковая операция недопустима: Control 'comboBoxPlatform 'доступ из потока, отличного от потока, в котором он был создан.

Что я могу обойти, вызвав Invoking, я на правильном пути?

Любая помощь будет оценена.

Ответы [ 2 ]

2 голосов
/ 12 января 2020

Как вы говорите, вам нужно заполнить comboBox из потока пользовательского интерфейса. Любая попытка получить к нему доступ из другого потока даст вам CrossThreadException. Самый простой способ, который я нашел для этого, - это вернуть информацию из Задачи, например, так:

    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.

Вот несколько простых изменений, которые передают результаты, полученные в режиме реального времени, обратно пользователю по мере выполнения задачи, связанной с ЦП.

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

    }
0 голосов
/ 12 января 2020

Как гласит ошибка, новый поток, который вы создаете, не может получить доступ к ComboBox, поскольку он не создан в этом новом потоке. У вас есть правильная идея с asyn c, хотя ждите.

Я думаю (и это только один из способов сделать это), вам лучше всего передать File.ReadAllText(@"Platforms\" + comboBoxPlatform.Text).Split('|'); в качестве параметра, чтобы к 1005 * не требовался доступ в новой теме .

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