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

WinForms中避免执行长任务的Car对象重复实例化方案咨询

解决方案:确保Car实例唯一且不阻塞UI

你的核心问题是并发请求同一CarId时会重复创建实例并多次调用WebService,同时Part获取ParentCar时用Wait()阻塞了UI线程。下面是分步的修复方案:

一、先分析现有代码的关键问题

  • CarCache用List<Car>,并发访问不安全,且无法跟踪正在加载中的Car实例——当第一个Car还在异步调用WebService时,后续请求会因为缓存没找到而重复创建。
  • Part.ParentCar的get方法用Task.Wait(),直接阻塞UI线程,违反WinForms的UI线程非阻塞原则。
  • Car构造函数里直接启动Task.Run,没有对外暴露这个异步任务的状态,调用者无法知道初始化是否完成。
  • DbClass.GetCarById没有处理并发场景,同一CarId的多个请求会触发多次数据库查询和WebService调用。

二、核心修复方案:用任务缓存代替实例缓存

我们需要用ConcurrentDictionary<string, Task<Car>>来缓存正在加载或已完成加载的Car任务,这样同一CarId的所有请求都会复用同一个Task,从根本上避免重复实例化和重复调用WebService。

1. 重构DbClass的缓存和GetCarById方法

public static class DbClass {
    private static SQLiteConnection sqlConn;
    private readonly string sqlFile = "pathToDbFile";
    // 用ConcurrentDictionary缓存Car的加载任务,键是CarId
    private static readonly ConcurrentDictionary<string, Task<Car>> _carTaskCache = new ConcurrentDictionary<string, Task<Car>>();

    public static async Task<Car> GetCarById(string carId, bool ignoreCache = false) {
        // 如果忽略缓存,先移除已有的任务
        if (ignoreCache) {
            _carTaskCache.TryRemove(carId, out _);
        }

        // 尝试获取缓存的任务,没有则创建新的加载任务
        var carTask = _carTaskCache.GetOrAdd(carId, async id => {
            Car foundCar = null;
            try {
                string sql = $"SELECT * FROM all_Cars WHERE Car = '{id}';";
                using (SQLiteCommand command = new SQLiteCommand(sql, sqlConn)) {
                    using (SQLiteDataReader reader = (SQLiteDataReader)await command.ExecuteReaderAsync()) {
                        if (await reader.ReadAsync()) { // 一个CarId对应一条记录,用if即可
                            foundCar = new Car(reader);
                            // 启动异步WebService任务,后台执行无需等待
                            _ = foundCar.FetchAdditionalDataAsync();
                        }
                    }
                }

                if (foundCar != null) {
                    var partsList = await GetPartsByCarId(carId);
                    if (partsList.Count > 0) {
                        foundCar.RelateParts(partsList);
                    }
                }
            } catch (Exception e) {
                // 记录异常日志
                string m = e.Message;
                // 加载失败时移除缓存任务,允许下次重试
                _carTaskCache.TryRemove(id, out _);
            }
            return foundCar;
        });

        // 等待任务完成,返回Car实例
        return await carTask;
    }

    // 修复原GetPartsByCarId的递归问题,改为实际数据库查询
    public static async Task<List<Part>> GetPartsByCarId(string carId) {
        List<Part> foundParts = new List<Part>();
        string sql = $"SELECT * FROM all_Parts WHERE ParentCarId = '{carId}';";
        using (SQLiteCommand command = new SQLiteCommand(sql, sqlConn)) {
            using (SQLiteDataReader reader = (SQLiteDataReader)await command.ExecuteReaderAsync()) {
                while (await reader.ReadAsync()) {
                    foundParts.Add(new Part(reader));
                }
            }
        }
        return foundParts;
    }

    // 调整GetCarIndexById,从任务缓存中获取已完成的实例
    public static int GetCarIndexById(string carId) {
        if (_carTaskCache.TryGetValue(carId, out var task) && task.IsCompletedSuccessfully) {
            var car = task.Result;
            // 兼容原CarCache逻辑,或直接用任务缓存替代
            return CarCache.FindIndex(c => c.Id == carId);
        }
        return -1;
    }

    // 用任务缓存同步生成CarCache,避免维护两个缓存
    private static List<Car> CarCache => _carTaskCache.Values
        .Where(t => t.IsCompletedSuccessfully)
        .Select(t => t.Result)
        .ToList();

    // 其他方法(InitiateSqlConnection等)保留,按需调整为异步
}

2. 重构Car类:把异步任务从构造函数移出来

构造函数只做同步的数据库加载,异步的WebService调用放到单独方法中,移除不可靠的析构函数:

public class Car {
    private List<Part> _parts = new List<Part>();
    public string Id { get; private set; }
    public ReadOnlyCollection<Part> Parts => _parts.AsReadOnly();

    // 构造函数仅同步加载数据库属性
    public Car(SQLiteDataReader sQLiteDataReader) {
        Id = sQLiteDataReader.GetString(sQLiteDataReader.GetOrdinal("Car"));
        // 加载其他数据库属性...
    }

    // 异步方法执行WebService调用
    public async Task FetchAdditionalDataAsync() {
        try {
            // 耗时30秒的WebService调用
            var additionalData = await CallLongRunningWebServiceAsync();
            // 填充更多属性
            // ...

            // 异步保存到数据库
            await SaveToInternalDbAsync();
        } catch (Exception e) {
            // 处理WebService调用异常
        }
    }

    private async Task SaveToInternalDbAsync() {
        int index = await Task.Run(() => DbClass.GetCarIndexById(Id));
        if (index > -1) {
            await DbClass.UpdateCarDataAsync(this);
        } else {
            await DbClass.AddCarAsync(this);
        }
    }

    public void RelateParts(IEnumerable<Part> parts) {
        // 加锁确保线程安全
        lock (_parts) {
            _parts.AddRange(parts);
        }
    }

    // 移除析构函数,改用异步任务完成后自动保存
}

3. 修复Part类:避免阻塞UI线程

把ParentCar改为异步方法,让UI层用await获取,避免阻塞:

public class Part {
    public string ParentCarId { get; private set; }

    // 异步获取父Car,调用者需用await
    public async Task<Car> GetParentCarAsync() {
        return await DbClass.GetCarById(ParentCarId);
    }

    // Part构造函数,从数据库加载ParentCarId
    public Part(SQLiteDataReader reader) {
        ParentCarId = reader.GetString(reader.GetOrdinal("ParentCarId"));
        // 加载其他属性...
    }
}

三、UI层调用注意事项

在WinForms事件中必须用await调用异步方法,绝对不能用Wait()或Result阻塞UI:

private async void btnLoadPart_Click(object sender, EventArgs e) {
    var part = await DbClass.GetPartById("part123"); // 假设GetPartById是异步方法
    var car = await part.GetParentCarAsync();
    // 更新UI控件
    lblCarId.Text = car.Id;
}

四、额外优化点

  • 异常日志:在异步方法和数据库操作中添加完善的日志记录,方便排查问题。
  • 缓存清理:可添加缓存过期逻辑,移除长时间未使用的Car任务。
  • 全异步化:将所有数据库操作改为异步版本(如ExecuteNonQueryAsync),避免阻塞线程。
  • 线程安全:对Car类的共享资源(如_parts)添加锁保护。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 09:56:55