首页 > 解决方案 > 集合被修改了 C# 异常但不知道为什么

问题描述

我在 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) {  }
}

基本上它只是一个网络爬虫。

标签: c#.net

解决方案


我认为线程是您的全部问题。 Thread.BeginCriticalRegion()不做你认为它做的事。从文档

通知主机执行即将进入代码区域,其中线程中止或未处理异常的影响可能会危及应用程序域中的其他任务。

换句话说,它不强制执行线程安全,它只是说“如果这坏了,它就会把所有东西都拿下来!”

你需要的是一个基本的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 与该对象一起使用:lock(SyncLock). 您也可以锁定列表本身,但是,一次只有一个线程可以锁定同步对象。为了帮助防止死锁,理想情况下,您的代码应该是您正在同步的任何对象上的唯一锁定源。你能保证列表类本身的东西不会被自己锁定吗?不用担心,制作自己的同步对象。没什么大不了的。

这是使用lock. 还有其他方法可以做到这一点。这个应该适合你。


推荐阅读