由于缓慢的HttpClient请求,在Parallel.ForEach内部阻塞了Task.Result

时间:2019-07-02 22:15:25

标签: c# .net .net-core-3.0

我了解在$elemMatch中使用异步lambda的含义,这就是为什么我不在这里使用它的原因。然后,这迫使我将Parallel.ForEach用于发出Http请求的每个任务。但是,通过性能分析器运行此简单的抓取工具表明.Result的已用独占时间%为〜98%,这显然是由于调用的阻塞性所致。

我的问题是:是否有可能对其进行优化以使其仍然异步?我不确定在这种情况下是否会有所帮助,因为检索HTML / XML可能会花费很长时间。

我正在运行一个具有8个逻辑核心的4核心处理器(因此.Result。现在我正在花大约2.5个小时来下载和解析约51,000个简单财务数据的HTML / XML页面。 / p>

我一直倾向于使用XmlReader而不是Linq2XML来加快解析速度,但是似乎瓶颈在MaxDegreesOfParallelism = 8调用上。

尽管这里无关紧要,但SEC将抓取限制为每秒10个请求。

.Result

2 个答案:

答案 0 :(得分:3)

该任务不受CPU限制,而是受网络限制,因此无需使用多个线程。

在一个线程上进行多个异步调用。 别等他们。将任务放在列表中。当您在那里达到一定数量时(例如,您想要一次10个),开始等待第一个完成(查找“任务,WhenAny”以获取更多信息)。

然后放上更多内容:-)然后,您可以使用其他代码以#/秒的速度控制任务的大小。

答案 1 :(得分:1)

  

是否有可能对其进行优化以使其仍然异步?

是的。我不确定为什么首先要使用Parallel;对于这种问题,这似乎是错误的解决方案。您需要对一组项目执行异步工作,因此更合适的方法是异步并发。这是通过Task.WhenAll完成的:

public class SECScraper
{
  public async Task DownloadAsync()
  {
    _numDownloaded = 0;
    _interval = _financeContext.Companies.Count() / 100;

    var tasks = _financeContext.Companies.Select(company => RetrieveSECDataAsync(company.CIK)).ToList();
    await Task.WhenAll(tasks);
  }

  private async Task RetrieveSECDataAsync(int cik)
  {
    var url = "https://www.sec.gov/cgi-bin/browse-edgar?action=getcompany&CIK=" + cik +
        "&type=10-q&dateb=&owner=include&count=100";

    var srBody = await ReadHTMLAsync(url);
    var srPage = new SearchResultsPage(srBody);

    var reportLinks = srPage.GetAllReportLinks();

    foreach (var link in reportLinks)
    {
      url = SEC_HOSTNAME + link;

      var fdBody = await ReadHTMLAsync(url);
      var fdPage = new FilingDetailsPage(fdBody);

      var xbrlLink = fdPage.GetInstanceDocumentLink();

      var xbrlBody = await ReadHTMLAsync(SEC_HOSTNAME + xbrlLink);
      var xbrlDoc = new XBRLDocument(xbrlBody);
      var epsData = xbrlDoc.GetAllEPSData();
    }

    IncrementNumDownloadedAndNotify();
  }

  private async Task<string> ReadHTMLAsync(string url)
  {
    using var response = await _client.GetAsync(url);
    return await response.Content.ReadAsStringAsync();
  }
}

此外,我建议您使用IProgress<T>报告进度。