.NET数据加载优化:DataGridView与SQLite异步加载性能问题
看起来你遇到的核心问题是串行异步操作导致的耗时累加,再加上一些SQLite连接管理、UI更新的小问题,才让21个玩家的刷新耗时拉到了7秒。下面针对你的场景给出具体的优化方案,帮你把20个玩家的刷新时间压到1秒以内:
1. 把串行异步改成并行批量处理(最核心的优化)
你的UpdateAllPlayers方法是循环里逐个await GetPlayerInfo,这相当于串行执行所有HTTP请求+数据库更新,每个请求都要等上一个完成,时间自然是累加的。改成并行发起所有任务,总耗时会接近单个请求的时间,而不是总和。
修改后的代码:
public async Task UpdateAllPlayers() { var allPlayers = await database.GetAllPlayers(); // 把所有更新任务收集起来,用Task.WhenAll并行执行 var updateTasks = allPlayers.Select(player => GetPlayerInfo(player.Name, player.ID, player.Type, true)); await Task.WhenAll(updateTasks); }
额外小优化:控制并发数避免限流
如果玩家数量扩展到120个,直接发起120个HTTP请求可能会被目标网站限流,或者导致本地网络压力过大。可以用SemaphoreSlim限制并发数:
// 定义一个全局的信号量,限制最多20个并发请求 private readonly SemaphoreSlim _httpRequestSemaphore = new SemaphoreSlim(20); public async Task GetPlayerInfo(string name, int id, string type, bool flag) { await _httpRequestSemaphore.WaitAsync(); try { // 你的GetPlayerInfo原有逻辑:HTTP请求+数据库更新 var website = $"website.com/playerdata/{id}.json"; var playerData = await GetUserDataFromJSONAsync(website); // 改成异步方法 await database.UpdatePlayer(playerData); } finally { _httpRequestSemaphore.Release(); // 无论成功失败都释放信号量 } }
2. 优化SQLite连接管理,减少连接开销
你的GetAllPlayers方法每次查询后都关闭连接,这会导致每次操作都要重新打开连接——连接的打开/关闭是有固定开销的。SQLite适合长期保持单个连接(只要配置正确的同步模式),不需要每次用完就关。
修改GetAllPlayers:
// 假设dbConnection是类级别的全局连接,在程序启动时打开,退出时再关闭 public async Task<List<Player>> GetAllPlayers() { var query = dbConnection.Table<Player>(); return await query.ToListAsync(); // 移除await conn.CloseAsync(); 不要在这里关闭连接 }
注意:SQLite异步操作需要确保连接字符串设置了合适的参数,比如:
"Data Source=yourdb.sqlite;Cache=Shared;Mode=ReadWriteCreate",避免线程冲突。
3. 优化DataGridView的更新方式,避免界面卡顿
清空再重新加载的方式会导致UI频繁重绘,产生卡顿。推荐两种优化方式:
方式一:用BindingList作为数据源,自动刷新
初始化时把DataGridView的数据源设置为BindingList<Player>,之后更新数据时直接操作BindingList,UI会自动刷新,不需要清空再绑定:
// 初始化时设置数据源 private void Form_Load(object sender, EventArgs e) { var playerList = await database.GetAllPlayers(); dataGridView1.DataSource = new BindingList<Player>(playerList); } // 添加玩家后直接更新BindingList public async void btnAddPlayer_Click(object sender, EventArgs e) { string website = "website.com/playerdata.json"; var player = await GetUserDataFromJSONAsync(website); await database.AddPlayer(player); var bindingList = (BindingList<Player>)dataGridView1.DataSource; bindingList.Add(player); }
方式二:刷新时暂停UI绘制
如果必须重新绑定数据源,先暂停DataGridView的绘制,完成后再恢复,减少卡顿:
private async Task RefreshDataGridView() { dataGridView1.SuspendLayout(); try { var allPlayers = await database.GetAllPlayers(); dataGridView1.DataSource = null; dataGridView1.DataSource = allPlayers; } finally { dataGridView1.ResumeLayout(); } }
4. 把最高价格计算交给SQLite,减少内存循环
你的Function里是在内存中遍历Price找最高值,其实可以直接在SQLite查询时完成计算,让数据库帮你做这个工作,比内存循环高效得多:
// 假设Price表和Player表通过PlayerID关联 var query = from player in dbConnection.Table<Player>() join price in dbConnection.Table<Price>() on player.ID equals price.PlayerID group price by player into playerGroup select new Player { ID = playerGroup.Key.ID, Name = playerGroup.Key.Name, // 直接在数据库计算最高价格和对应日期 HighestPrice = playerGroup.Max(p => p.Price), HighestPriceDate = playerGroup .Where(p => p.Price == playerGroup.Max(p2 => p2.Price)) .Select(p => UnixToUTC(p.UnixTime)) .FirstOrDefault() }; var allPlayers = await query.ToListAsync();
5. 优化添加玩家的异步逻辑
把btnAddPlayer里的同步HTTP请求改成异步,避免阻塞UI线程;同时用HttpClient代替HttpWebRequest,异步支持更友好(注意不要每次创建新的HttpClient,用静态实例复用):
// 全局复用HttpClient,不要每次创建 private static readonly HttpClient _httpClient = new HttpClient(); public async void btnAddPlayer_Click(object sender, EventArgs e) { string website = "website.com/playerdata.json"; var player = await GetUserDataFromJSONAsync(website); await database.AddPlayer(player); // 刷新UI(用BindingList的话直接Add即可) var bindingList = (BindingList<Player>)dataGridView1.DataSource; bindingList.Add(player); } private async Task<Player> GetUserDataFromJSONAsync(string url) { var json = await _httpClient.GetStringAsync(url); return JsonConvert.DeserializeObject<Player>(json); }
按照这些优化点调整后,20个玩家的刷新时间应该能控制在1秒以内——并行HTTP请求会把原来的串行耗时压缩到单个请求的时间,再加上连接管理和UI更新的优化,完全能达到你的预期。
内容的提问来源于stack exchange,提问作者takoz53

