我想用一个包含大量有趣数据页面的网站,但由于源非常大,我想多线程并限制过载。
我使用Parallel.ForEach
来启动10个任务的每个块,然后在主for
循环中等待,直到活动线程的数量开始下降到阈值以下。为此,我使用活动线程的计数器,在启动具有WebClient
的新线程时递增,并在触发DownloadStringCompleted
的{{1}}事件时递减。
最初的问题是如何使用WebClient
而不是DownloadStringTaskAsync
,并等待DownloadString
中启动的每个线程都已完成。这已通过解决方法解决:
主要foor循环中的计数器(Parallel.ForEach
)和activeThreads
。
使用Thread.Sleep
代替await DownloadStringTaskAsync
是否应该通过在等待DownloadString数据到达时释放线程来提高速度?
回到最初的问题,有没有办法在没有涉及计数器的解决方法的情况下更优雅地使用TPL?
DownloadString
答案 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完成,您可以自由继续。
记住“亲吻”