【问题标题】:speed up WebClient calls inside a foreach loop在 foreach 循环中加速 WebClient 调用
【发布时间】:2019-03-16 20:18:42
【问题描述】:

我正在开发一个 asp.net mvc-5 Web 应用程序,并且我有以下调用来对第 3 方应用程序进行连续的 WebClient() 调用:

public async Task<List<Technology>> GetResource(int? filtertype)
{

  try
  {
     using (WebClient wc = new WebClient()) 
     {
         string url = currentURL + "resources?AUTHTOKEN=" + token;
         var json = await wc.DownloadStringTaskAsync(url);
         resourcesinfo = JsonConvert.DeserializeObject<ResourcesInfo>(json);
     }

     //for each resource get its tag + add the tag to the list
     foreach (var c in resourcesinfo.operation.Details)
     {    
        ResourceAccountListInfo resourceAccountListInfo = new ResourceAccountListInfo();
        using (WebClient wc = new WebClient()) 
        {    
        string url = currentURL + "resources/" + c.RESOURCEID + "?AUTHTOKEN=" + token;
        string tempurl = url.Trim();    
        var json = await wc.DownloadStringTaskAsync(tempurl);
        resourceAccountListInfo = JsonConvert.DeserializeObject<ResourceAccountListInfo>(json);     
                       AllTags.Add(resourceAccountListInfo.SingleOrDefault().CUSTOMFIELDVALUE.ToLower());   
     }    
   }
}

目前第一个WebClient 将返回大约1,500 条记录,所以我在foreach 中的第二个WebClient 调用将执行1,500 次,因此整个过程大约需要20 分钟才能完成。那么我该如何改进这个过程呢?

【问题讨论】:

  • 是否需要等到1500个请求全部完成后才响应用户?
  • 在这种情况下,我想知道是否有办法对它们进行批处理。目前主要是大声思考,所以我没有代码可以回答。但不是在循环的每次迭代中调用await,而是一次循环5条记录(可配置),构建wc.DownloadStringTaskAsync(tempurl)结果的List&lt;Task&lt;T&gt;&gt;,然后在该列表上执行WaitAll .因此,您一次将有 5 个并行 HTTP 请求,而不是 1 个。根据需要调整 5 的值。
  • 可能有一种更简洁的方法,但我的第一个方法是为每个Task&lt;T&gt; 分配一个.ContinueWith(),因为它被添加到列表中。在该函数中,您可以对响应做任何您需要做的事情(似乎将其添加到AllTags)。因此,当WaitAll() 完成时,该批次应该被添加到AllTags 并且是时候开始下一个批次了。您需要确保在.ContinueWith()WaitAll() 中也有一些仔细的错误处理。在未等待的任务中,错误很容易被忽视。
  • 向服务 1 发出 1500 多个请求是非常站不住脚的。在这里没有什么能真正帮助你;这是不可行的。大多数设计良好的 API 将提供某种方式来批处理请求(因为它们不希望您点击 1500 次以上),或者它们会提供某种机制来将相关项目包含在初始请求中。很多支持分页,这样可以进一步减少每个请求的负载。如果您控制 API,则需要实现这些功能,否则您需要更好地阅读文档或向 API 所有者投诉。
  • 如果您真的没有其他选择,那么您只需要围绕它编写代码。也许只需列出初始请求的结果,并通过链接或按钮查询每个选择加入的项目的详细信息。至少这样,用户可以快速地获得他们目前需要的东西。

标签: c# asp.net parallel-processing task-parallel-library


【解决方案1】:

您需要一些方法来限制对 DownloadStringTaskAsync 的调用。您可以使用信号量和 Task.Run 手动执行此操作,也可以使用 TPL Dataflow 库来提供所有 url 并将并行度指定为所需的限制。数据流块将接受异步委托(与 Parallel.For 不同)

private static async Task<Thing[]> ProcessAllUrls(string[] urls)
{
    var workBlock = new TransformBlock<string, Thing>(
        async url => await DownloadAndProcessUrl(url),
        new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 20 }
        );

    var outputBlock = new BufferBlock<Thing>();

    using (workBlock.LinkTo(outputBlock, new DataflowLinkOptions { PropagateCompletion = true }))
    {

        foreach (var url in urls)
        {
            workBlock.Post(url);
        }

        // signal no more input going into workblock
        workBlock.Complete();

        // wait for workblock to pump all data into outputblock
        await workBlock.Completion;

        IList<Thing> finalResult = null;
        bool result = outputBlock.TryReceiveAll(out finalResult);
        return finalResult.ToArray();
    }
}

不过,您确实需要小心在 Web 服务器进程中执行并行操作。虽然 WebClient 调用与 CPU 真正异步,但反序列化响应的工作将在线程池线程上运行,这意味着它在此期间与 ASP.NET 请求竞争 CPU 资源

【讨论】:

  • 你能告诉我如何转换我原来的网络客户端调用来使用你的方法吗?第二点您能否详细说明“您对响应进行反序列化的工作将在线程池线程上运行,这意味着它在此期间与 ASP.NET 对 CPU 资源的请求竞争”到底是什么意思?在 Web 服务器中运行并行内容的缺点?
  • 在您的原始示例中,您正在遍历 Details 集合,然后 (1) 构建 url,(2) 调用 url 和 (3) 反序列化结果。如果您只是做了第一步并首先构建了所有 url,那么您可以将它们输入到我发布的内容中。 DownloadAndProcessUrl 调用将执行步骤 2 和 3 并返回一个技术。 (在我的示例中,我将输出称为事物)。在 Web 服务器内部,每个传入请求都使用线程池中的一个线程。您使用的每个并行线程也来自池。其中一项越多,就会影响另一项的表现。
猜你喜欢
  • 2013-12-31
  • 1970-01-01
  • 2015-03-03
  • 2014-12-24
  • 1970-01-01
  • 1970-01-01
  • 2015-07-04
  • 1970-01-01
  • 2019-09-29
相关资源
最近更新 更多