我正在构建一个网络爬虫,看来TPL非常适合我的用例。这是我想到的流程:
GetCommentsFromA --| |-- NotifyC
|-- ExtractKeywords --|
GetCommentsFromB --| |-- NotifyD
基本上,组件有三种类型:收集器,转换器和通知器。收集者通过抓取不同的网站来收集评论,然后将评论传递给转换器。他们应该每X秒轮询一次网页。在这种情况下,唯一存在的转换器就是负责从注释中提取关键字的转换器。他们本质上是永无休止的生产者。
我最初是通过扩展经典的Producer-Consumer模型来实现的。我在收集器和转换器之间放置了一个缓冲块,在转换器和通知程序之间放置了一个缓冲块。 (实际上还有更多事情在发生,但这是事实。)每个组件都独立地发布和接收来自其相应缓冲区的数据。就是说,我觉得必须有一个更优雅的方法。
从那时起,我发现LinkTo
允许块相互连接,并且除了缓冲块之外还有专门的块,因此我开始考虑以下方面:
var extractKeywords = new TransformBlock<string, string>(ExtractKeywords);
var notifyTarget = new ActionBlock<string, string>(NotifyTarget);
extractKeywords.LinkTo(notifyTarget);
然后我可以启动不同的收集器,或多或少在单独的线程上执行以下操作:
public async Task CollectAsync()
{
while (true)
{
Page page = await website.DownloadLatestPageAsync();
foreach (string comment in page.Comments())
{
transformer.Post(comment);
}
await Task.Delay(ArbitraryDelay);
}
}
收集器将发布到前面的代码段中看到的转换块。但是,我确实对此方法有所保留。首先,(这可能是由于我对异步,多线程和TPL缺乏了解),我担心发布到转换块会导致延迟。理想情况下,我希望收集者花费尽可能少的时间将数据传输到转换器,但是我似乎无法找出一种干净的方法。如果转换器变慢,则调用Post
似乎会阻塞一段时间,而且我不确定SendAsync
是否也可以解决此问题。 Task.Run
似乎是个坏主意……但我不知道。感觉就像我接近了,但是错过了一些简单的事情。