gpt4 book ai didi

c# - 使用异步 CTP 同时下载 HTML 页面

转载 作者:行者123 更新时间:2023-11-30 13:40:00 25 4
gpt4 key购买 nike

尝试使用 Async CTP 编写 HTML 爬虫时,我陷入了如何编写无递归方法来完成此任务的困境。

这是我目前的代码。

private readonly ConcurrentStack<LinkItem> _LinkStack;
private readonly Int32 _MaxStackSize;
private readonly WebClient client = new WebClient();

Func<string, string, Task<List<LinkItem>>> DownloadFromLink = async (BaseURL, uri) =>
{
string html = await client.DownloadStringTaskAsync(uri);
return LinkFinder.Find(html, BaseURL);
};

Action<LinkItem> DownloadAndPush = async (o) =>
{
List<LinkItem> result = await DownloadFromLink(o.BaseURL, o.Href);
if (this._LinkStack.Count() + result.Count <= this._MaxStackSize)
{
this._LinkStack.PushRange(result.ToArray());
o.Processed = true;
}
};

Parallel.ForEach(this._LinkStack, (o) =>
{
DownloadAndPush(o);
});

但显然这并不像我希望的那样有效,因为在 Parallel.ForEach 执行第一次(也是唯一一次迭代)时,我只有一个项目。我能想到的使 ForEach 递归的最简单方法,但我不能(我不认为)这样做,因为我会很快耗尽堆栈空间。

任何人都可以指导我如何重组此代码,以创建我将描述为递归延续的内容,该递归延续会添加项目,直到达到 MaxStackSize 或系统内存不足?

最佳答案

我认为使用 C# 5/.Net 4.5 执行此类操作的最佳方法是使用 TPL Dataflow .甚至还有a walkthrough on how to implement web crawler using it .

基本上,您创建一个“ block ”来负责下载一个 URL 并从中获取链接:

var cts = new CancellationTokenSource();

Func<LinkItem, Task<IEnumerable<LinkItem>>> downloadFromLink =
async link =>
{
// WebClient is not guaranteed to be thread-safe,
// so we shouldn't use one shared instance
var client = new WebClient();
string html = await client.DownloadStringTaskAsync(link.Href);

return LinkFinder.Find(html, link.BaseURL);
};

var linkFinderBlock = new TransformManyBlock<LinkItem, LinkItem>(
downloadFromLink,
new ExecutionDataflowBlockOptions
{ MaxDegreeOfParallelism = 4, CancellationToken = cts.Token });

您可以将 MaxDegreeOfParallelism 设置为您想要的任何值。它表示最多可以同时下载多少个 URL。如果你根本不想限制它,你可以将它设置为 DataflowBlockOptions.Unbounded

然后你创建一个 block ,以某种方式处理所有下载的链接,比如将它们全部存储在一个列表中。它还可以决定何时取消下载:

var links = new List<LinkItem>();

var storeBlock = new ActionBlock<LinkItem>(
linkItem =>
{
links.Add(linkItem);
if (links.Count == maxSize)
cts.Cancel();
});

由于我们没有设置 MaxDegreeOfParallelism,它默认为 1。这意味着在这里使用非线程安全的集合应该没问题。

我们再创建一个 block :它将从 linkFinderBlock 获取一个链接,并将其传递给 storeBlock 并返回给 linkFinderBlock

var broadcastBlock = new BroadcastBlock<LinkItem>(li => li);

其构造函数中的 lambda 是一个“克隆函数”。如果需要,您可以使用它来创建项目的克隆,但这里没有必要,因为我们不会在创建后修改 LinkItem

现在我们可以将 block 连接在一起:

linkFinderBlock.LinkTo(broadcastBlock);
broadcastBlock.LinkTo(storeBlock);
broadcastBlock.LinkTo(linkFinderBlock);

然后我们可以通过将第一个项目提供给 linkFinderBlock(或 broadcastBlock,如果您还想将其发送给 storeBlock)来开始处理:

linkFinderBlock.Post(firstItem);

最后等到处理完成:

try
{
linkFinderBlock.Completion.Wait();
}
catch (AggregateException ex)
{
if (!(ex.InnerException is TaskCanceledException))
throw;
}

关于c# - 使用异步 CTP 同时下载 HTML 页面,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9259689/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com