我有一个网站列表和一个代理服务器列表。
我有这个动作
Action<string> action = (string url) =>
{
var proxy = ProxyHandler.GetProxy();
HtmlDocument html = null;
while (html == null)
{
try
{
html = htmlDocumentLoader.LoadDocument(url, proxy.Address);
// Various db manipulation code
ProxyHandler.ReleaseProxy(proxy);
}
catch (Exception exc)
{
Console.WriteLine("{0} proxies remain", ProxyHandler.ListSize());
// Various db manipulation code
proxy = ProxyHandler.GetProxy();
}
}
};
我打电话使用
urlList.AsParallel().WithDegreeOfParallelism(12).ForAll(action);
或
Parallel.ForEach(urlList, action);
我的ProxyHandler类如下
public static class ProxyHandler
{
static List<Proxy> ProxyList = new ProxyRepository().GetAliveProxies().ToList();
public static Proxy GetProxy()
{
lock (ProxyList)
{
while (ProxyList.Count == 0)
{
Console.WriteLine("Sleeping");
Thread.Sleep(1000);
}
var proxy = ProxyList[0];
ProxyList.RemoveAt(0);
return proxy;
}
}
public static void ReleaseProxy(Proxy proxy)
{
lock (ProxyList)
{
if(!ProxyList.Contains(proxy))ProxyList.Add(proxy);
}
}
public static int ListSize()
{
lock (ProxyList)
{
return ProxyList.Count;
}
}
}
我的问题是,当执行此操作时,它似乎可以很快完成约90%的网站,然后花费很长时间来完成其余的工作。
我的意思是,在100个网址中,花费尽可能多的时间来完成前90个,就像它在过去10个时一样。
我已经排除代理已经死亡,因为没有抛出任何异常。看起来好像urlList上的最后一个项目需要很长时间才能完成。
更新
我正在添加一些运行数据,以使我的问题更加清晰:
Minute 1 2 3 4 5 6 7 8 9 16 18 19
Count 23 32 32 17 6 1 1 1 1 2 1 2
正如你在前4分钟看到的,我做了104/119的请求。其余的需要15分钟。
这似乎是加入Threads时的一个问题,但我没有发现它可能是什么。
答案 0 :(得分:3)
你在浪费线程和CPU时间。在这种情况下,你将有12个线程;每个线程一次只处理一个url。因此,您一次只能处理12个网址。而且,大多数情况下,这些线程不会做任何事情(它们只是等待一个免费代理或一个加载的页面),而它们可以用于更有用的任务。
为避免这种情况,您应该使用非阻塞IO操作。因此,您应该考虑使用其异步接口之一(htmlDocumentLoader.LoadDocument
/ htmlDocumentLoader.BeginLoadDocument
或htmlDocumentLoader.EndLoadDocument
/ htmlDocumentLoader.LoadDocumentAsync
),而不是使用htmlDocumentLoader.LoadDocumentCompleted
。在这种情况下,如果你有100个url,它们将同时加载而不会创建额外的线程并浪费CPU时间。只有在加载页面时,才会创建新线程(实际上是从ThreadPool中获取)来处理它。
等待免费代理的方式也很浪费。如果没有免费代理,请使用冻结线程的while (ProxyList.Count == 0)
,而不是使用定时器,它会每秒唤醒并检查免费代理是否可用。它不是最好的解决方案,但至少它不会浪费线程。更好的解决方案是向ProxyHandler添加一个事件,该事件将在代理可用时通知。
答案 1 :(得分:2)
您的问题可能是由于PLinq使用了分区程序。
如果正在使用Range Partitiner,则您的url集合将被拆分为每组中具有相等(ish)数量的url的组。然后为每个组启动任务,没有进一步的同步。
这意味着将有一个任务花费最长时间,并且在所有其他任务完成后仍有工作要做。这实际上意味着操作的最后一部分是单线程的。
解决方案是使用不同的分区程序。您可以使用内置的Chunk Partitioner,如MSDN所述。
如果这种方法不能很好地工作,则必须编写/找到一个逐个生成元素的分区器实现。这内置于C#5:EnumerablePartitionerOptions