You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

HtmlAgilityPack与Parallel.Invoke多线程调用异常问题及解决方法

解决HtmlAgilityPack结合Parallel.Invoke多线程调用的异常问题

这个问题我之前也碰到过,核心原因很明确:HtmlAgilityPack的HtmlDocument实例不是线程安全的。你现在的Table类里,所有方法共享了同一个doc对象,当Parallel.Invoke同时触发Foo1、Foo2、Foo3时,多个线程在同时修改这个共享的HtmlDocument,直接导致内部的HtmlNodeCollection在执行Append操作时出现并发冲突,也就是你看到的那个异常。

给你几个可行的解决方案,按推荐程度排序:

方案1:每个方法内部创建独立的HtmlDocument实例(最推荐)

既然每个方法都是独立加载自己的HTML内容,完全没必要共享同一个文档实例。修改你的Table类,让每个方法自己创建专属的HtmlDocument:

public class Table 
{ 
    public void Foo1() 
    { 
        HtmlDocument doc = new HtmlDocument();
        doc.LoadHtml("some html"); 
        // 这里写针对当前doc的业务操作
    } 

    public void Foo2() 
    { 
        HtmlDocument doc = new HtmlDocument();
        doc.LoadHtml("some html"); 
        // 这里写针对当前doc的业务操作
    } 

    public void Foo3() 
    { 
        HtmlDocument doc = new HtmlDocument();
        doc.LoadHtml("some html"); 
        // 这里写针对当前doc的业务操作
    } 
}

这种方式完全避免了线程间的资源竞争,不需要任何同步锁,能完美保留Parallel.Invoke的并发性能,是最适合你当前场景的方案。

方案2:用ThreadLocal实现线程内的实例复用

如果你的场景需要在同一个线程的多次方法调用中复用HtmlDocument(比如同一个线程连续调用Foo1和Foo2),可以用ThreadLocal来确保每个线程拥有自己的专属实例:

public class Table 
{ 
    // 每个线程会生成自己的HtmlDocument实例,线程间互不干扰
    private ThreadLocal<HtmlDocument> _threadDoc = new ThreadLocal<HtmlDocument>(() => new HtmlDocument());

    public void Foo1() 
    { 
        var doc = _threadDoc.Value;
        doc.LoadHtml("some html"); 
        // 业务操作
    } 

    public void Foo2() 
    { 
        var doc = _threadDoc.Value;
        doc.LoadHtml("some html"); 
        // 业务操作
    } 

    public void Foo3() 
    { 
        var doc = _threadDoc.Value;
        doc.LoadHtml("some html"); 
        // 业务操作
    } 
}

这种方式既实现了同一线程内的实例复用,又保证了线程安全,性能也不会受太大影响。

方案3:给共享实例加同步锁(不推荐,仅作备选)

如果你的业务逻辑必须让所有方法共享同一个HtmlDocument实例(比如所有方法都要修改同一个文档内容),那只能通过加锁来强制串行访问:

public class Table 
{ 
    private HtmlDocument doc = new HtmlDocument();
    // 专门用于锁的对象,推荐用私有专属对象而非this
    private readonly object _lockObj = new object();

    public void Foo1() 
    { 
        lock(_lockObj)
        {
            doc.LoadHtml("some html"); 
            // 业务操作
        }
    } 

    public void Foo2() 
    { 
        lock(_lockObj)
        {
            doc.LoadHtml("some html"); 
            // 业务操作
        }
    } 

    public void Foo3() 
    { 
        lock(_lockObj)
        {
            doc.LoadHtml("some html"); 
            // 业务操作
        }
    } 
}

注意:这种方式会让Parallel.Invoke的并发调用退化为串行执行,完全失去多线程的性能优势,所以只有在万不得已的情况下才考虑使用。

总结一下:优先选方案1,因为你的每个方法都是独立处理HTML,完全不需要共享实例,这样既能解决线程安全问题,又能保留多线程的效率。

内容的提问来源于stack exchange,提问作者utop

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 09:37:26