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.
我们该如何纠正?
不幸的是,(以前)被接受的答案继续走错了路。这里的问题是"我最终需要通过在工作线程上执行异步操作来从错误的线程更新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);
}
}));