2015-12-20 39 views
0

代碼C#任務和無效的方法

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 Forms和在練習C#,林相當新的這種語言,但知道一點蟒蛇林。

基本上,即時嘗試要做的是,您在textBox1上輸入url,當您點擊button1時,它會轉到該網址並提取所有鏈接的文本。

append這些結果listView1但我不斷收到此錯誤

錯誤消息:

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

我們該如何糾正呢?

+0

我得到了這個工作。通過使返回的東西,然後在'ContinueWith'方法這樣運行它的另一種方法: 'ContinueWith(T => other_method_that_processes_results(t.Result)..........)' 然而,我想只用一種方法就可以做到這一點。但顯然這是不允許的 – Zion

+0

在SO上提供了正確的解決方案太多次。讓我們假裝OP已經搜索,並發現它沒有什麼有趣...或簡單downvote的問題... –

回答

8

不幸的是,(先前)接受的答案繼續走下了錯誤的道路,最終導致您首先失敗。這裏的問題是「我最終需要通過在工作線程上執行異步操作來更新來自錯誤線程的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

+1

儘可能將所有東西放在同一根線上。 我會記住這一點。 好吧不需要亂用'ContinueWith'嗎?我討厭過時的教程。但是你也找不到更新的。 感謝您爲此,我只是按照它的教程,它提到'ContinueWith'當然是新的C#我什麼都不知道,所以我在這裏問。 但謝謝你清理。 async並等待它是 – Zion

+0

@Zion:'await'實質上是指「該方法的其餘部分是任務的'ContinueWith';'await'表達式的值是結果」。在C#5中應該有很多關於使用async/await的教程。一個好的開始在這裏:https://blogs.msdn.microsoft.com/ericlippert/2011/10/03/async-articles/但是那裏還有更多。 –

3

您需要從GUI線程訪問它。 WInforms爲此提供了invoke命令。

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

'listView1.Invoke()'? 即時通訊不是C#專家,但'listView1'是一個列表視圖,而不是'事件'。 不是'invoke'用於'events'嗎? – Zion

+0

是的,沒有。 WinForms控件提供了這種方法供您在他們的UI消息循環中輸入命令。 – srandppl

+0

我明白你在說什麼,但是在代碼中我們調用listView? 我們是否在這一行中調用它? 'Task.Factory.StartNew(()=> get_url_contents(textBox1.Text))ContinueWith(T => t.Id,TaskScheduler.FromCurrentSynchronizationContext());' – Zion