task.RESULT在并行内部阻塞



我理解与 Parallel.ForEach一起使用异步lambda的含义,这就是为什么我在这里不使用它的原因。然后,这迫使我使用.Result来完成提出HTTP请求的每个任务。但是,通过性能探查器运行这个简单的刮刀表明.Result具有〜98%的独家时间%,这显然是由于呼叫的封锁性。

我的问题是:是否有可能将其优化以使其仍然是异步?我不确定在这种情况下这会有所帮助,因为它可能需要这么长时间才能检索HTML/XML。

我正在运行一个带有8个逻辑内核的4个核心处理器(因此MaxDegreesOfParallelism = 8。现在,我正在寻找大约2.5个小时的下载和解析〜51,000 HTML/XML简单财务数据的页面。

我倾向于使用xmlreader而不是linq2xml来加快解析,但似乎瓶颈在.Result调用中。

尽管在这里不重要,但SEC限制了10个请求/秒

public class SECScraper
{
    public event EventHandler<ProgressChangedEventArgs> ProgressChangedEvent;
    public SECScraper(HttpClient client, FinanceContext financeContext)
    {
        _client = client;
        _financeContext = financeContext;
    }
    public void Download()
    {
        _numDownloaded = 0;
        _interval = _financeContext.Companies.Count() / 100;
        Parallel.ForEach(_financeContext.Companies, new ParallelOptions {MaxDegreeOfParallelism = 8},
            company =>
            {
                RetrieveSECData(company.CIK);
            });
    }
    protected virtual void OnProgressChanged(ProgressChangedEventArgs e)
    {
        ProgressChangedEvent?.Invoke(this, e);
    }
    private void RetrieveSECData(int cik)
    {
        // move this url elsewhere
        var url = "https://www.sec.gov/cgi-bin/browse-edgar?action=getcompany&CIK=" + cik +
                  "&type=10-q&dateb=&owner=include&count=100";
        var srBody = ReadHTML(url).Result; // consider moving this to srPage
        var srPage = new SearchResultsPage(srBody);
        var reportLinks = srPage.GetAllReportLinks();
        foreach (var link in reportLinks)
        {
            url = SEC_HOSTNAME + link;
            var fdBody = ReadHTML(url).Result;
            var fdPage = new FilingDetailsPage(fdBody);
            var xbrlLink = fdPage.GetInstanceDocumentLink();
            var xbrlBody = ReadHTML(SEC_HOSTNAME + xbrlLink).Result;
            var xbrlDoc = new XBRLDocument(xbrlBody);
            var epsData = xbrlDoc.GetAllEPSData();
            //foreach (var eps in epsData)
            //    Console.WriteLine($"{eps.StartDate} to {eps.EndDate} -- {eps.EPS}");
        }
        IncrementNumDownloadedAndNotify();
    }
    private async Task<string> ReadHTML(string url)
    {
        using var response = await _client.GetAsync(url);
        return await response.Content.ReadAsStringAsync();
    }
}

任务不是CPU绑定,而是网络绑定,因此无需使用多个线程。

在一个线程上进行多个async调用。只是不等待它们。将任务放在列表中。当您在那里获得一定的金额(假设您要一次进行10次(,开始等待第一个完成(查找"任务,何时"以获取更多信息(。

然后在:-)然后使用其他代码通过#/秒控制任务的大小。

是否有可能优化它仍然是异步的?

是。我不确定为什么您首先使用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>进行报告进度。

最新更新