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

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

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

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

回到原来的问题,是否有办法更优雅地使用TPL,而没有涉及计数器的解决方法?

 private static volatile int activeThreads = 0; public static void RecordData() { var nbThreads = 10; var source = db.ListOfUrls; // Thousands urls var iterations = source.Length / groupSize; for (int i = 0; i  RecordUri(item)); //I want to wait here until process further data to avoid overload while (activeThreads > 30) Thread.Sleep(100); } } private static async Task RecordUri(Uri uri) { using (WebClient wc = new WebClient()) { Interlocked.Increment(ref activeThreads); wc.DownloadStringCompleted += (sender, e) => Interlocked.Decrement(ref iterationsCount); var jsonData = ""; RootObject root; jsonData = await wc.DownloadStringTaskAsync(uri); var root = JsonConvert.DeserializeObject(jsonData); RecordData(root) } } 

如果您想要一个优雅的解决方案,您应该使用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(jsonData) }; IDisposable subscription = query.Subscribe(x => { /* Do something with x.uri && x.json */ }); 

这就是整个代码。 这是很好的multithreading,它一直在控制之下。

只需NuGet“System.Reactive”即可得到这些位。

 Parallel.ForEach 

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

 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中使用同步方法 。 线程将由PLinq完成,您可以自由继续。

记住“亲吻”