2012-07-13 144 views
1

我有一個C#程序,當前同時從幾個站點下載數據,之後代碼對我已下載的數據做了一些處理。我正試圖移動這個來異步執行我的下載,然後處理我下載的數據。我在測序時遇到了一些麻煩。以下是我正在使用的代碼的快照:等待循環中的異步方法完成

class Program 
{ 
    static void Main(string[] args) 
    { 
     Console.WriteLine("Started URL downloader"); 
     UrlDownloader d = new UrlDownloader(); 
     d.Process(); 
     Console.WriteLine("Finished URL downloader"); 

     Console.ReadLine(); 
    } 
} 

class UrlDownloader 
{ 
    public void Process() 
    { 
     List<string> urls = new List<string>() { 
      "http://www.stackoverflow.com", 
      "http://www.microsoft.com", 
      "http://www.apple.com", 
      "http://www.google.com" 
     }; 

     foreach (var url in urls) 
     { 
      WebClient Wc = new WebClient(); 
      Wc.OpenReadCompleted += new OpenReadCompletedEventHandler(DownloadDataAsync); 
      Uri varUri = new Uri(url); 
      Wc.OpenReadAsync(varUri, url); 
     } 
    } 

    void DownloadDataAsync(object sender, OpenReadCompletedEventArgs e) 
    { 
     StreamReader k = new StreamReader(e.Result); 
     string temp = k.ReadToEnd(); 
     PrintWebsiteTitle(temp, e.UserState as string); 
    } 

    void PrintWebsiteTitle(string temp, string source) 
    { 
     Regex reg = new Regex(@"<title[^>]*>(.*)</title[^>]*>"); 
     string title = reg.Match(temp).Groups[1].Value; 

     Console.WriteLine(new string('*', 10)); 
     Console.WriteLine("Source: {0}, Title: {1}", source, title); 
     Console.WriteLine(new string('*', 10)); 
    } 
} 

基本上,我的問題是這樣的。從上面我的輸出是:

Started URL downloader 
Finished URL downloader 
"Results of d.Process()" 

我想要做的就是完成d.Process()方法,然後返回到我的計劃類「主」的方法。所以,我在找的輸出是:

Started URL downloader 
"Results of d.Process()" 
Finished URL downloader 

我d.Process()方法異步運行,但我無法弄清楚如何等待我所有的處理,以恢復我的主要方法之前完成。關於如何在C#4.0中做到這一點的任何想法?我不知道如何去'告訴'我的Process()方法等到它的所有異步活動完成後才返回Main方法。

+0

關於異步操作存在多個問題:一個示例http://stackoverflow.com/questions/6906778/how-to-wait-on-multiple-asynchronous-operation-completion – IAbstract 2012-07-13 17:06:08

+1

您使用的是什麼版本的C#? .Net 4.0使用Task對象提供TPL。 – IAbstract 2012-07-13 17:07:24

+0

您可以執行OpenRead,它會同步執行並阻止當前線程。 – 2012-07-13 17:13:19

回答

8

如果你是在.NET> = 4.0,你可以使用TPL

Parallel.ForEach(urls, url => 
{ 
    WebClient Wc = new WebClient(); 
    string page = Wc.DownloadString(url); 
    PrintWebsiteTitle(page); 
}); 

我也將使用HtmlAgilityPack,而不是解析正則表達式的頁面。

void PrintWebsiteTitle(string page) 
{ 
    HtmlAgilityPack.HtmlDocument doc = new HtmlAgilityPack.HtmlDocument(); 
    doc.LoadHtml(page); 
    Console.WriteLine(doc.DocumentNode.Descendants("title").First().InnerText); 
} 
+0

謝謝@ L.B這個很棒!我是新來的異步編程,所以不熟悉TPL。 – armohan 2012-07-13 17:28:08

0

我會推薦使用WebClient.DownloadDataAsync而不是自己寫。然後,您可以使用任務並行庫來包裝調用DownloadDataAsync在TaskCompletionSource獲得多個任務對象,你可以等待或繼續:

 webClient.DownloadDataAsync(myUri); 
     webClient.DownloadDataCompleted += (s, e) => 
              { 
              tcs.TrySetResult(e.Result); 
              }; 

     if (wait) 
     { 
      tcs.Task.Wait(); 
      Console.WriteLine("got {0} bytes", tcs.Task.Result.Length); 
     } 
     else 
     { 
      tcs.Task.ContinueWith(t => Console.WriteLine("got {0} bytes", t.Result.Length)); 
     } 

處理錯誤情況,可以擴大使用TaskCompletionSource的:

webClient.DownloadDataCompleted += (s, e) => 
           { 
          if(e.Error != null) tcs.SetException(e.Error); 
          else if(e.Cancelled) tcs.SetCanceled(); 
          else tcs.TrySetResult(e.Result); 
           }; 

做同樣多的任務:

Task.WaitAll(tcs.Task, tcs2.Task); 

Task.Factory.ContinueWhenAll(new Task[] {tcs.Task, tcs2.Task}, ts => 
                { 
                 /* do something with all the results */ 
                });