我理解与 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>
进行报告进度。