【问题标题】:Working with multiple http request-response处理多个 http 请求-响应
【发布时间】:2014-08-30 10:40:57
【问题描述】:

我有一个循环遍历应用列表的程序。

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 调用,例如,

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

这给了我这样的回应,

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

我将这些输入到内部数据表中。但是现在,这个程序需要很长时间才能完成(3 小时),因为我有接近 2000 个 appid,每个 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 详细信息放入一个公共文件中的最佳方法是什么?

【问题讨论】:

  • 这会生成 2000 个后台工作人员...不好...而是从一个后台工作人员调用您的 webapi 异步...并在异步 webrequest 完成事件之一触发并写入 xml 文件时使用锁定.
  • 能否请您添加您的DoWork 方法代码?
  • @rene ,那么我必须设置的限制是多少?另外,如果我设置了 5 个后台工作人员的限制,这是否意味着线程将并行运行,直到处理完所有 2K url?
  • @YuvalItzchakov 。目前,在我的 DoWork 中,我有一些代码可以从我得到的 XML 响应中构建一个字符串,然后执行一个简单的 System.IO.File.WriteAllText(filename,stringvalue)
  • 试试Parallel.ForParallel.ForEach 更简单,不用担心线程太多会导致性能下降。

标签: c# xml multithreading http .net-4.0


【解决方案1】:

HTTP 请求本质上是 IO 绑定和异步的,没有理由使用后台工作人员来完成您需要的工作。

您可以通过Microsoft.Bcl.AsyncHttpClient 使用与.NET 4 兼容的async-await

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

这样,您可以同时处理所有请求,并在它们完成后处理所有请求的结果。

【讨论】:

  • @rene 如果需要,OP 可以在Task.WhenAll 之后同步写入,尽管我认为他没有理由这样做。
  • @mhn 如果需要,我使用 StreamWriter 添加了异步写入文件的代码。
  • 尽管按照您的建议添加了 Microsoft.Bcl.Async 和 HttpClient,但我收到“找不到类型或命名空间异步”错误。任何指针?
  • 偶然发现了stackoverflow.com/questions/19421878/…。但我没有安装 VS 2012 :(
  • 可以免费下载VS2012 express。
【解决方案2】:

此解决方案适用于 .Net 2.0 及更高版本,方法是使用 WebClient 类中的异步方法并使用随 Interlocked 类和普通 lock 递减的计数器将结果序列化写入文件。

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

【讨论】:

  • 您真的看到对文件进行如此多的同步写入而不是汇总结果并写入一次,完全省去锁争用的好处吗?
  • 我不确定这种情况,但如果结果足够大,内存可能是个问题。或者如果发生故障并且重新运行成本很高,您将获得中间结果(但这需要重新启动逻辑)。我更担心打开这么多网络连接的可能性。
  • 如果需要,他总是可以限制请求。他还可以使用Task.WhenAny 处理它们。
猜你喜欢
  • 2011-02-23
  • 1970-01-01
  • 2010-12-17
  • 2013-06-22
  • 1970-01-01
  • 1970-01-01
  • 2021-05-26
  • 1970-01-01
  • 2021-12-02
相关资源
最近更新 更多