我在 C# 中有这段代码:
Thread.BeginCriticalRegion();
if(visitedUrls.Contains(url) || visitedUrls.Where( x => x.Contains(root) ).Count() > 150) {
return;
}
else{
visitedUrls.Add(url);
}
Thread.EndCriticalRegion();
这是一个被多个不同进程调用的函数,这就是为什么我(试图)使它成为线程安全的。
异常 Collection was modified; enumeration operation may not execute
在 if
行上引发,但如果我将其保留为
if(visitedUrls.Contains(url)
效果很好,为什么?
编辑 这是实际代码:
public void scrapAzienda(String url, String root_url, int depth)
{
if (depth <= 0) return;
var web = new HtmlWeb();
HtmlNode[] nodes = null;
HtmlDocument doc = null;
HtmlNode bodyNode = null;
Thread.BeginCriticalRegion();
if (urlVisitati.Contains(url) || urlVisitati.Where(x => x.Contains(root_url)).Count() > 150)
return;
else
urlVisitati.Add(url);
Thread.EndCriticalRegion();
try
{
doc = web.Load(url, Proxy.getUrl(), Proxy.getPort(), Proxy.getUsername(), Proxy.getPassword());
nodes = doc.DocumentNode.SelectNodes("//a[@href]").ToArray() ?? null;
foreach (HtmlNode item in nodes)
{
Task.Factory.StartNew(() => scrapAzienda(item.Attributes["href"].Value, root_url, depth - 1), TaskCreationOptions.AttachedToParent);
}
GC.Collect();
if (doc != null)
{
bodyNode = doc.DocumentNode.SelectSingleNode("//body");
cercaNumeri(bodyNode.InnerText, url);
cercaEmail(bodyNode.InnerText, url);
}
}
catch (Exception) { }
}
基本上它只是一个网络爬虫。
答案 0 :(得分:4)
我认为线程是您的全部问题。 Thread.BeginCriticalRegion()
并没有按照您的想法行事。来自docs:
通知主机执行即将进入一个代码区域,在该区域中线程中止或未处理的异常可能危及应用程序域中的其他任务。
换句话说,它不强制执行线程安全,它只是说“如果它坏了,它就会把所有东西都拿下来!”
您需要的是一个基本的lock
:
lock(someObj)
{
if (urlVisitati.Contains(url) || urlVisitati.Where(x => x.Contains(root_url)).Count() > 150)
{
return;
}
else
{
urlVisitati.Add(url);
}
}
someObj
需要是一个静态对象。每个线程都需要引用同一个对象。我通常为此目的在类级别创建一个基本对象:
private static readonly object SyncLock = new object();
然后您对该对象使用锁:lock(SyncLock)
。您也可以锁定列表本身,但是,一次只有一个线程可以锁定同步对象。为了帮助防止死锁,理想情况下,您的代码应该是您同步对象的唯一锁定源。你能保证列表类本身内部的东西不会被锁定吗?别担心,制作自己的同步对象。没什么大不了的。
这是关于 lock
线程的速成课程。还有其他方法可以做到这一点。这个应该适合你。