2014-08-30 137 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