并行请求刮取网站的多个页面

时间:2017-09-18 17:10:26

标签: c# multithreading async-await webclient parallel.foreach

我想用一个包含大量有趣数据页面的网站,但由于源非常大,我想多线程并限制过载。 我使用Parallel.ForEach来启动10个任务的每个块,然后在主for循环中等待,直到活动线程的数量开始下降到阈值以下。为此,我使用活动线程的计数器,在启动具有WebClient的新线程时递增,并在触发DownloadStringCompleted的{​​{1}}事件时递减。

最初的问题是如何使用WebClient而不是DownloadStringTaskAsync,并等待DownloadString中启动的每个线程都已完成。这已通过解决方法解决: 主要foor循环中的计数器(Parallel.ForEach)和activeThreads

使用Thread.Sleep代替await DownloadStringTaskAsync是否应该通过在等待DownloadString数据到达时释放线程来提高速度?

回到最初的问题,有没有办法在没有涉及计数器的解决方法的情况下更优雅地使用TPL?

DownloadString

2 个答案:

答案 0 :(得分:2)

如果您想要一个优雅的解决方案,您应该使用Microsoft的Reactive Framework。这很简单:

var source = db.ListOfUrls; // Thousands urls

var query =
    from uri in source.ToObservable()
    from jsonData in Observable.Using(
        () => new WebClient(),
        wc => Observable.FromAsync(() => wc.DownloadStringTaskAsync(uri)))
    select new { uri, json = JsonConvert.DeserializeObject<RootObject>(jsonData) };

IDisposable subscription =
    query.Subscribe(x =>
    {
        /* Do something with x.uri && x.json */
    });

这就是整个代码。它很好地支持多线程,并且可以控制它。

只需NuGet“System.Reactive”获取位。

答案 1 :(得分:-1)

Parallel.ForEach

将创建ProcessorCount任务以执行源Enumerable中每个项目的功能。它将注意没有很多任务,并将等待执行所有项目和任务。

Task.WhenAll

只等待给定的任务不执行它们。它在你手上以适当的方式执行它们而不是一次执行它们。

但是你的代码有些错误。函数RecordUri将返回一个必须等​​待的任务,否则ForEach将创建越来越多的函数,因为函数永远不会知道当前任务何时完成。同样有问题的是你在一个任务中创建一个任务,而第一个任务什么都不做,然后等待第一个任务。

你可能还想看看Parallel.ForEach的这个重载 https://msdn.microsoft.com/en-us/library/dd782934(v=vs.110).aspx

修改

  

使用等待DownloadStringTaskAsync而不是DownloadString应该通过在等待DownloadString数据到达时释放线程来提高速度吗?

没有。当任务正在等待外部资源时,它进入Suspended状态(Windows api没有使用一些旧的/脏迭代等待)。所以没有太大区别。 不同之处在于编译异步代码时编译器将产生的开销。 DownloadStringTaskAsync将创建包含长操作的任务。如果您使用等待它,您将自己附加到该任务(通过ContinueWith)。所以你只需创建一个等待另一个的任务。这是我在上面文中讨论的开销。

我的方法是:使用Parallel.ForEach中的synchronous method。线程将由PLinq完成,您可以自由继续。

记住“亲吻”