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

多异步方法并发调用同一函数引发随机错误排查求助

并发调用异步任务时随机异常的排查与解决

问题背景

同步执行流程完全正常,但通过Task.Run启动多个异步任务并发调用addGroupStringToDictionary函数时,会出现随机错误,且每次错误触发情况不固定。


相关代码

异步任务定义

//Some other tasks before
Task<bool> taskIfcQuantityArea = Task.Run<bool>(() =>
{
    return this.addGroupStringToDictionary("IfcQuantityArea");
});
Task<bool> taskIfcQuantityLength = Task.Run<bool>(() =>
{
    return this.addGroupStringToDictionary("IfcQuantityLength");
});
Task<bool> taskIfcSiUnit = Task.Run<bool>(() =>
{
    return addGroupStringToDictionary("IfcSiUnit");
});
Task<bool> taskIfcPropertySingleValue = Task.Run<bool>(() =>
{
    return addGroupStringToDictionary("IfcPropertySingleValue");
});
//uses IfcPerson, IfcOrganization
Task<bool> taskIfcPersonAndOrganization = Task.Run<bool>(() =>
{
    return addGroupStringToDictionary("IfcPersonAndOrganization");
});
//uses IfcOrganization
Task<bool> taskIfcApplication = Task.Run(async () =>
{
    await taskIfcSiUnit;
    return addGroupStringToDictionary("IfcApplication");
});
//uses IfcSiUnit
Task<bool> taskIfcMeasureWithUnit = Task.Run(async () =>
{
    await taskIfcSiUnit;
    return addGroupStringToDictionary("IfcMeasureWithUnit");
});
//some other tasks after.

addGroupStringToDictionary函数

private bool addGroupStringToDictionary(string typeName)
{
    //int processCount = await Task.Run<int>(() =>
    //{
    GroupedListStrings groupElt = this.listGrouppedStrings.FirstOrDefault(x => x.Type == typeName.ToUpper());
    if (groupElt != null)
    {
        List<string> listStringInGroup = groupElt.ListStrings;
        foreach (string line in listStringInGroup)
        {
            try
            {
                if(typeName== "IfcLocalPlacement($")
                {
                    typeName = "IfcLocalPlacement";
                }
                var type = Type.GetType("Ifc."+typeName);
                if (typeName == "IfcPropertySingleValue" || typeName == "IfcDirection" || typeName == "IfcSiUnit" || typeName == "IfcQuantityLength" || typeName == "IfcQuantityArea" || typeName == "IfcQuantityVolume" || typeName == "IfcQuantityWeight")
                {
                    try
                    {
                        object instance = Activator.CreateInstance(type, line);
                        this.addToListDictionary((IfcElement)instance);
                    }
                    catch
                    {

                    }
                }
                else if (typeName == "IfcOpeningElement")
                {
                    try
                    {
                        object instance = Activator.CreateInstance(type, line, this.listDictionaries, this.DictionaryBolts);
                        this.addToListDictionary((IfcElement)instance);
                    }
                    catch
                    {

                    }
                }
                else
                {
                    try
                    {
                        object instance = Activator.CreateInstance(type, line, this.listDictionaries);
                        this.addToListDictionary((IfcElement)instance);
                    }
                    catch
                    {

                    }
                }
            }
            catch
            {
                this.addError(line);
            }
        }
        this.listGrouppedStrings.Remove(groupElt);
        this.reportProgressImport();
    }
    //return 100;
    //});
    this.reportProgressImport();
    return true;
}

addToListDictionary函数

private void addToListDictionary(IfcElement elt)
{
    if(elt.ErrorFound)
    {
        this.listReadButError.Add(elt);
        return;
    }
    string type = elt.GetType().ToString();
    if (elt is IfcRepere)
    {
        type = "Ifc.IfcRepere";
    }
    else if (elt is IfcRepereType)
    {
        type = "Ifc.IfcRepereType";
    }
    else if (elt is IfcPhysicalSimpleQuantity)
    {
        type = "Ifc.IfcPhysicalSimpleQuantity";
    }
    else if (elt is IfcProfileDef)
    {
        type = "Ifc.IfcProfileDef";
    }
    else if (elt is IfcGeometricRepresentationContext)
    {
        type = "Ifc.IfcGeometricRepresentationContext";
    }
    GroupDictionary group = this.ListDictionaries.FirstOrDefault(x => x.Name == type);
    if(group==null)
    {
        group = new GroupDictionary { Name = type };
        this.ListDictionaries.Add(group);
    }
    group.ListElements[elt.ID] = elt;

    if (elt is IfcMechanicalFastener)
    {
        IfcMechanicalFastener bolt = (IfcMechanicalFastener)elt;
        this.DictionaryBolts[bolt.Tag] = bolt;
    }
    else if(elt is IfcProject)
    {
        this.listProjects.Add((IfcProject)elt);
    }
    else if(elt is IfcElementAssembly ifcAss)
    {
        this.DictionaryIfcElementAssemblies[ifcAss.Key] = ifcAss;
    }
}

相关成员定义

private List<GroupDictionary> listDictionaries = new List<GroupDictionary>();
public List<GroupDictionary> ListDictionaries { get { return this.listDictionaries; } set { this.listDictionaries = value; } }

GroupDictionary类

public class GroupDictionary
{
    string name { get; set; }
    public string Name { get { return this.name; } set { this.name = value; } }
    public ConcurrentDictionary<int, IfcElement> ListElements = new ConcurrentDictionary<int, IfcElement>();
    public GroupDictionary()
    {

    }
}

原因确认

你的猜测完全正确,问题根源在于非线程安全的共享集合被多线程并发操作:

  1. listGrouppedStrings:List<T>本身不支持线程安全,并发执行FirstOrDefault查询和Remove删除操作时,会导致枚举器失效、元素丢失或索引错乱。
  2. ListDictionaries:多线程同时执行FirstOrDefault查询和Add添加操作,可能出现重复添加相同GroupDictionary的情况,或查询后添加前被其他线程修改导致逻辑错误。
  3. listReadButError、listProjects等List<T>集合,同样存在并发添加的线程安全问题。

解决方案

1. 替换为线程安全集合

将非线程安全集合替换为System.Collections.Concurrent命名空间下的线程安全集合,从根源避免并发问题:

  • ListDictionaries替换为ConcurrentDictionary:
    private ConcurrentDictionary<string, GroupDictionary> dictDictionaries = new ConcurrentDictionary<string, GroupDictionary>();
    public ConcurrentDictionary<string, GroupDictionary> DictDictionaries { get { return dictDictionaries; } }
    
    对应修改addToListDictionary中的分组获取逻辑:
    // 自动处理不存在键的情况,线程安全
    GroupDictionary group = dictDictionaries.GetOrAdd(type, key => new GroupDictionary { Name = key });
    
  • listGrouppedStrings替换为ConcurrentDictionary:
    private ConcurrentDictionary<string, GroupedListStrings> dictGrouppedStrings = new ConcurrentDictionary<string, GroupedListStrings>();
    
    修改addGroupStringToDictionary中的获取和删除逻辑:
    // 原子性移除元素,避免并发冲突
    if (dictGrouppedStrings.TryRemove(typeName.ToUpper(), out var groupElt))
    {
        // 处理groupElt的业务逻辑
    }
    
  • 对于listReadButError、listProjects等集合,若无需顺序要求可替换为ConcurrentBag<T>,若需顺序则用lock保护操作。

2. 使用锁保护临界区

如果无法替换集合类型,在访问共享集合的代码块上添加锁,确保同一时间只有一个线程操作:

  • listGrouppedStrings操作加锁:
    private readonly object listGrouppedLock = new object();
    
    // ...
    
    GroupedListStrings groupElt;
    lock(listGrouppedLock)
    {
        groupElt = this.listGrouppedStrings.FirstOrDefault(x => x.Type == typeName.ToUpper());
        if(groupElt != null)
        {
            this.listGrouppedStrings.Remove(groupElt);
        }
    }
    if(groupElt != null)
    {
        // 后续处理逻辑(已移除集合,无需锁)
    }
    
  • ListDictionaries操作加锁:
    private readonly object dictListLock = new object();
    
    // ...
    
    GroupDictionary group;
    lock(dictListLock)
    {
        group = this.ListDictionaries.FirstOrDefault(x => x.Name == type);
        if(group==null)
        {
            group = new GroupDictionary { Name = type };
            this.ListDictionaries.Add(group);
        }
    }
    group.ListElements[elt.ID] = elt;
    
    注意:锁对象应为只读私有字段,避免嵌套锁,防止死锁。

3. 优化任务依赖逻辑

目前部分任务依赖taskIfcSiUnit,需确保依赖关系正确,但即使有依赖,其他无依赖任务仍会并发执行,因此共享资源的线程安全处理仍是核心。


内容的提问来源于stack exchange,提问作者Siegfried.V

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 00:35:17