C # tasks and void methods

code

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) { } } } 

I use Windows forms and practice C #, I'm pretty new to this language, but I know a little python.

basically what i am trying to do is enter the url on textBox1 and when you press button1 it will go to that url and it will extract all the link text.

and append those results to listView1 however I keep getting this error

error message:

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

How do we fix this?

0
source share
2 answers

The (previously) accepted answer, unfortunately, continues to go the wrong way that you hit in the first place. The problem here is that "I had to update the user interface from the wrong thread by performing an asynchronous operation on the workflow." The above solution: "have a worker thread, marshal, call back to the user interface thread." The best solution is not trying to get the user interface to work with the workflow in the first place.

Also there is no need to guess with ContinueWith ; in C # 5 and above, we have an asynchronous wait.

Suppose we really have work that we want to do in another thread. (This is suspicious: there is no reason why the high-latency operation here should go on a different thread, and not on the processor! But for the sake of argument, let's assume that we want to load HTML into another thread:

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

Note that I noted the async method. This does not make it work in another thread. This means that "this method will return to its caller - the message loop that sent the event - before the method runs. It will resume inside the method at some point in the future."

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

What do we have? We create an asynchronous task that loads some HTML and returns the task. Then we expect this task. Waiting for a task means "return immediately to my caller - the message loop that sent the click button - so that the user interface runs. When the task finishes, this method will resume here and get the value calculated by the task."

Now the rest of the program is completely normal:

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

We are still in the user interface thread; we are in the button handler. The only work that was done in another thread was to get the HTML, and when it was available, we resumed work here.

Now there are several problems.

What happens if the button is pressed again while we wait for the HTML to load? We are trying to download it again! It would be nice to turn off the button before waiting and come back again after.

Also, as I said earlier, why are we creating a thread for a network operation? You want to send a letter to your aunt and get an answer; You donโ€™t need to hire an employee to take the letter to the mailbox, mail it, and then sit at the mailbox, waiting for an answer. Most of the operation will be carried out by the post office; you donโ€™t need to hire an employee to do nothing but to nurse mail. The same thing here. The vast majority of the work will be carried out by the network; why do you hire a thread to look after it? Just find the asynchronous HTML loading method that returns the job and waits for the task to complete. HttpClient.GetAsync immediately comes to mind, although there may be others.

Third problem: we created an object in the workflow. Who says it's safe to use it in a user interface thread? There are many "stream models" that an object can have; in the COM world, they are traditionally called "apartments" (you can talk to me only on the topic in which you created me), "rent" (you can talk to me on any topic, but you must make sure that none of the two attempts at the same time), "free" (everything goes, the object is safe) and several others. It is assumed that the object in question is safe to โ€œrentโ€ or better โ€” reading will not occur in the user interface thread until writing is done in the workflow. But if the object is truly an โ€œapartment,โ€ then you have an object that you cannot talk to on any thread except the work thread that you just threw away. This is a potential real mess.

The moral of the story here is, firstly, to keep everything in one thread as much as possible , and the second not to turn your program inside out to make the work asynchronous; just use await .

+8
source

You need to access it from the GUI thread. WInforms provides an invoke command for this ocassion.

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

All Articles