码
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.
基本上,什么即时试图做的是你键入一个url
上textBox1
,当你点击button1
它会去那个网址,并提取所有的链接的文本.
然而,append
这些结果listView1
我不断收到此错误
错误信息:
Additional information: Cross-thread operation not valid: Control 'listView1' accessed from a thread other than the thread it was created on.
我们如何纠正这个?
不幸的是,(先前)接受的答案继续沿着你最终的错误路径继续前进.这里的问题是"我最终需要通过在工作线程上执行异步操作来从错误的线程更新UI".给出的解决方案是"让工作线程将回调封送回UI线程".更好的解决方案是不要首先尝试在工作线程上进行UI工作.
也没有必要捣乱ContinueWith
; 在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
.