2014-08-30 133 views
3

我有一個程序循環遍歷應用程序列表。現在使用多個http請求響應

Apps 
-------- 
App1 
App2 
App3 

,爲他們每個人,我做一個HTTP請求得到建立的每個應用程序作爲XML的列表。

所以請求一樣,

http://example.com/getapplist.do?appid=App1 

給我的迴應一樣,

<appid name="App1"> 
    <buildid BldName="Bld3" Status="Not Ready"></buildid> 
    <buildid BldName="Bld2" Status="Ready"></buildid> 
    <buildid BldName="Bld1" Status="Ready"></buildid> 
</appid> 

現在我得到最高版本號狀態 「就緒」,然後做另一個Web api call like,

http://example.com/getapplist.do?appid=App1&bldid=Bld2 

這給了我一個迴應,

<buildinfo appid="App1" buildid="Bld2" value="someinfo"></build> 

我把它們送入內部數據表。但是現在,這個程序需要花費很長時間才能完成(3個小時),因爲我有近2000個appids,每個id有2個Web請求。我嘗試使用指定hereBackgroundWorker對此問題進行分類。我想將所有來自http響應的信息整理成單個XML,然後使用該XML進行進一步處理。這引發錯誤,

文件正被另一個進程

所以我的代碼看起來像,

if (!backgroundWorker1.IsBusy) 
{ 
    for(int i = 0; i < appList.Count; i++) 
    { 
     BackgroundWorker bgw = new BackgroundWorker(); 
     bgw.WorkerReportsProgress = true; 
     bgw.WorkerSupportsCancellation = true;      
     bgw.DoWork += new DoWorkEventHandler(bgw_DoWork);     
     bgw.ProgressChanged += new ProgressChangedEventHandler(bgw_ProgressChanged); 
     bgw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgw_RunWorkerCompleted); 
     //Start The Worker 
     bgw.RunWorkerAsync(); 
    } 
} 

而且DoWork功能選取的變量值,並將其轉換爲XML 。

我可以通過所有後臺工作人員的所有http響應將app- buildinfo詳細信息轉換爲普通文件的最佳方式是什麼?

+1

這會產生2000 backgroundworkers ......不好......而不是打電話給你的WebAPI異步從一個BackgroundWorker的...並使用鎖的WebRequest完成的事件火災和寫入xml文件異步的時候之一。 – rene 2014-08-30 10:48:03

+0

你能否加上你的'DoWork'方法代碼? – 2014-08-30 11:07:38

+0

@rene,那麼我有什麼限制?另外,如果我設置了5個背景工作者的限制,那麼這是否意味着線程將並行運行,直到所有2K網址都可以工作? – mhn 2014-08-30 11:11:05

回答

4

HTTP請求是IO綁定的並且本質上是異步的,沒有理由使用後臺工作來完成你所需要的。

您可以通過Microsoft.Bcl.AsyncHttpClient利用async-await這是在.NET兼容4:

private async Task ProcessAppsAsync(List<string> appList) 
{ 
    var httpClient = new HttpClient(); 

    // This will execute your IO requests concurrently, 
    // no need for extra threads. 
    var appListTasks = appList.Select(app => httpClient.GetAsync(app.Url)).ToList(); 

    // Wait asynchronously for all of them to finish 
    await Task.WhenAll(appListTasks); 

    // process each Task.Result and aggregate them to an xml 
    using (var streamWriter = new StreamWriter(@"PathToFile") 
    { 
     foreach (var appList in appListTasks) 
     { 
      await streamWriter.WriteAsync(appList.Result); 
     } 
    } 
} 

這樣,您同時處理所有的請求,一旦他們已經完成了處理來自所有這些結果。

+0

@rene在'Task.WhenAll'之後,如果需要,OP可以同步寫入,儘管我沒有理由讓他這麼做。 – 2014-08-30 12:41:05

+1

我將嘗試這種方法並更新結果 – mhn 2014-08-30 13:01:43

+0

@mhn如果需要,我使用'StreamWriter'異步添加代碼寫入文件。 – 2014-08-30 13:17:10

0

該解決方案通過使用來自WebClient類中的異步方法和使用遞減與Interlocked類反和普通lock序列化的結果,文件的書寫工作爲.NET 2.0及以上。

var writer = XmlWriter.Create(
    new FileStream("api.xml", 
        FileMode.Create)); 
writer.WriteStartElement("apps"); // root element in the xml 
// lock for one write 
object writeLock = new object(); 
// this many calls    
int counter = appList.Count; 

foreach (var app in appList) 
{ 
    var wc = new WebClient(); 

    var url = String.Format(
     "http://example.com/getapplist.do?appid={0}&bldid=Bld2", 
     app); 
    wc.DownloadDataCompleted += (o, args) => 
     { 
      try 
      { 
       var xd = new XmlDocument(); 
       xd.LoadXml(Encoding.UTF8.GetString(args.Result)); 
       lock (writeLock) 
       { 
        xd.WriteContentTo(writer); 
       } 
      } 
      finally 
      { 
       // count down our counter in a thread safe manner 
       if (Interlocked.Decrement(ref counter) == 0) 
       { 
        // this was the last one, close nicely 
        writer.WriteEndElement(); 
        writer.Close(); 
        ((IDisposable) writer).Dispose(); 
       } 
      } 
     }; 
    wc.DownloadDataAsync(
     new Uri(url)); 
} 
+0

您是否真的看到在對一個文件進行如此多的同步寫入而不是聚合結果和寫入一次,而完全沒有鎖定爭用方面看到了好處? – 2014-08-30 13:30:44

+0

I'我不確定這種情況,但是如果結果足夠大,內存可能會成爲問題;或者如果出現故障並且重新運行代價昂貴,則會產生中間結果(但這需要重新啓動邏輯)。更關心有多少網絡連接打開的可能性 – rene 2014-08-30 13:52:56

+0

如果需要的話,他總是可以抑制請求,他們也可以在完成時使用Task.W來處理它們henAny'。 – 2014-08-30 16:11:27