C#任务和void方法

本文关键字:方法 void 任务 | 更新日期: 2023-09-27 18:27:41

代码

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using HtmlAgilityPack;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            textBox1.Text = "place url hear";
        }
        private void Form1_Load(object sender, EventArgs e)
        {
        }
        private void textBox1_TextChanged(object sender, EventArgs e)
        {
        }
        private void button1_Click(object sender, EventArgs e)
        {
            Task.Factory.StartNew(() => get_url_contents(textBox1.Text)).ContinueWith(t => t.Id, TaskScheduler.FromCurrentSynchronizationContext());
        }

        private void get_url_contents(string url)
        {
            var doc = new HtmlWeb().Load(url);
            HtmlNodeCollection nodes = doc.DocumentNode.SelectNodes("//a");

            foreach(HtmlNode node in nodes)
            {
                listView1.Items.Add(node.InnerText);
            }
        }
        private void listView1_SelectedIndexChanged(object sender, EventArgs e)
        {
        }
    }
}

我使用windows窗体,正在练习C#,我对这种语言相当陌生,但知道一点python。

基本上,我想做的是在textBox1上键入url,当你单击button1时,它会转到该url并提取所有链接的文本。

append这些结果到listView1,然而我一直得到这个错误

错误消息:

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

我们该如何纠正?

C#任务和void方法

不幸的是,(以前)被接受的答案继续走错了路。这里的问题是"我最终需要通过在工作线程上执行异步操作来从错误的线程更新UI"。给出的解决方案是"让工作线程将调用封送回UI线程"。更好的解决方案是,一开始就不要试图在工作线程上进行UI工作。

也没有必要用CCD_ 7来搅乱;在C#5和更高版本中,我们有异步等待。

让我们假设我们确实有工作要在另一个线程上执行。(这很可疑;这里的高延迟操作没有理由需要在另一个线程上进行。它不受处理器限制!但为了便于讨论,我们假设我们希望在另一线程上下载HTML:

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

请注意,我已将方法标记为async。这不会使它在另一个线程上运行。这意味着"在方法的工作完成之前,该方法将返回给它的调用者——调度事件的消息循环。它将在未来的某个时候在方法内部恢复。"

        var doc = await Task.Factory.StartNew(() => new HtmlWeb().Load(url));

我们这里有什么?我们派生出一个异步任务,该任务加载一些HTML并返回一个任务。然后我们等待这项任务。等待任务意味着"立即返回给我的调用者——同样是发送按钮点击的消息循环——以保持UI运行。当任务完成时,此方法将在此处恢复,并获得任务计算的值。"

现在程序的其余部分完全正常:

        HtmlNodeCollection nodes = doc.DocumentNode.SelectNodes("//a");
        foreach(HtmlNode node in nodes)
        {
            listView1.Items.Add(node.InnerText);
        }
    }

我们仍在UI线程上;我们在按钮点击处理程序中。在另一个线程上所做的唯一工作就是获取HTML,当它可用时,我们立即恢复。

现在,这里有一些问题。

如果在等待加载HTML时再次单击该按钮,会发生什么?我们尝试再次加载它!最好在等待之前关闭按钮,然后再打开。

此外,正如我之前提到的,为什么我们要为网络绑定操作生成线程?你想给你阿姨寄封信,然后得到回复;你不必雇一个工人把信拿到邮箱,邮寄,然后坐在邮箱旁等待回复。大部分业务将由邮局完成;你不需要雇一个工人来照看邮局。这里也是一样。绝大多数工作将由网络完成;你为什么要雇一个线程来照顾它?只需找到一个异步HTML加载方法,返回一个任务,然后等待该任务。HttpClient.GetAsync立刻浮现在脑海中,尽管可能还有其他的。

第三个问题:我们在工作线程上创建了一个对象谁说在UI线程上使用它是安全的一个对象可以有许多"线程模型";在COM世界中,这些传统上被称为"公寓"(你只能在你创建我的线程上与我交谈)、"出租"(你可以在任何线程上与我们交谈,但你必须确保没有两个线程同时尝试)、"免费"(任何事都可以,对象是安全的)和其他一些。这里的假设是,有问题的对象可以安全地"出租"或更好——在工作线程上完成写入之前,读取不会发生在UI线程上。但是,如果对象本身真的是"公寓",那么你有一个对象,除了你刚刚扔掉的工作线程之外,你不能在任何线程上与之交谈。这可能真是一团糟。

这个故事的寓意是,首先,尽可能让所有东西都在同一个线程上,其次不要把你的程序翻过来让异步工作;只需使用await

您需要从GUI线程访问它。WInformas为该ocation提供invoke命令。

listView1.Invoke(() => {
            foreach(HtmlNode node in nodes)
            {
                listView1.Items.Add(node.InnerText);
            }
}));