加载数据至DataGridView时窗口冻结,为何线程方案无效?
问题描述
在按钮点击事件中,前两个无UI更新的方法通过Task.Run执行时不会导致窗口卡顿,但执行loadFoldersToGrid方法时窗口会冻结(加载完成后恢复正常),即便使用Task.Run结合BeginInvoke仍未解决,相关代码如下:
private async void btnSearch_Click(object sender, EventArgs e) { btnFolder.Enabled = false; btnSearch.Enabled = false; btnStop.Enabled = true; try { var foldersTask = Task.Run(() => GetAccessibleDirectories(txtFolderPath.Text, chkSubFolders.Checked)); var folders = await foldersTask; var BF = await Task.Run(() => getFolderdetails(folders)); await Task.Run(() => this.BeginInvoke(new Action(() => loadFoldersToGrid(BF)))); // Runs on UI thread } finally { btnFolder.Enabled = true; btnSearch.Enabled = true; btnStop.Enabled = false; } }
private async void loadFoldersToGrid(BrowserFolders BF) { int i = 0; long MBcon = 1048576; long MB, Total = 0; dgFolders.Rows.Clear(); dgFolders.Rows.Add(BF.folders.Count); foreach (var rec in BF.folders) { dgFolders.Rows[i].Cells[0].Value = i; dgFolders.Rows[i].Cells[1].Value = rec.folderPath; dgFolders.Rows[i].Cells[2].Value = rec.fileCount; long folderSize = GetFolderSize(rec.folderPath); string megabytes = (folderSize / MBcon).ToString("F1"); dgFolders.Rows[i].Cells[3].Value = megabytes; dgFolders.Rows[i].Cells[4].Value = rec.fileCreated.ToString("MM/dd/yyyy"); Total = Total + folderSize; i += 1; } txtFolderCount.Text = BF.folders.Count.ToString(); txtSize.Text = string.Format("{0:N0} MB", Total / MBcon); }
请问该问题的原因是什么?异步任务为何无法解决?
原因分析
loadFoldersToGrid本质仍在UI线程执行同步阻塞操作:外层的Task.Run完全多余,BeginInvoke本身就是把委托投递到UI线程消息队列执行,最终loadFoldersToGrid的所有代码还是在UI线程运行。GetFolderSize是耗时IO操作,直接阻塞UI线程:循环中每次调用GetFolderSize都会同步读取磁盘,占用UI线程时间片,导致窗口无法响应刷新、用户输入等事件,表现为冻结。- 假异步的
async方法:loadFoldersToGrid标记了async但内部没有任何await操作,方法会同步执行到底,不会释放UI线程。
解决方法
1. 把耗时IO操作移到后台线程执行
提前在后台计算好所有文件夹的大小,避免在UI线程做磁盘读取:
private async void btnSearch_Click(object sender, EventArgs e) { btnFolder.Enabled = false; btnSearch.Enabled = false; btnStop.Enabled = true; try { var folders = await Task.Run(() => GetAccessibleDirectories(txtFolderPath.Text, chkSubFolders.Checked)); var BF = await Task.Run(() => getFolderdetails(folders)); // 后台计算所有文件夹大小 var foldersWithSize = await Task.Run(() => BF.folders.Select(rec => new { Rec = rec, Size = GetFolderSize(rec.folderPath) }).ToList() ); // 仅在UI线程做纯界面更新 loadFoldersToGrid(foldersWithSize); } finally { btnFolder.Enabled = true; btnSearch.Enabled = true; btnStop.Enabled = false; } }
2. 优化UI更新逻辑,减少频繁刷新
修改loadFoldersToGrid,只做纯UI操作,同时暂停DataGridView绘制以减少刷新开销:
// 移除不必要的async,当前无异步操作 private void loadFoldersToGrid(List<dynamic> foldersWithSize) { int i = 0; long MBcon = 1048576; long Total = 0; dgFolders.SuspendLayout(); dgFolders.Rows.Clear(); dgFolders.Rows.Add(foldersWithSize.Count); foreach (var item in foldersWithSize) { var rec = item.Rec; long folderSize = item.Size; string megabytes = (folderSize / MBcon).ToString("F1"); dgFolders.Rows[i].Cells[0].Value = i; dgFolders.Rows[i].Cells[1].Value = rec.folderPath; dgFolders.Rows[i].Cells[2].Value = rec.fileCount; dgFolders.Rows[i].Cells[3].Value = megabytes; dgFolders.Rows[i].Cells[4].Value = rec.fileCreated.ToString("MM/dd/yyyy"); Total += folderSize; i++; } txtFolderCount.Text = foldersWithSize.Count.ToString(); txtSize.Text = string.Format("{0:N0} MB", Total / MBcon); dgFolders.ResumeLayout(); }
3. 推荐使用数据绑定替代逐行赋值
创建ViewModel类整理数据,直接绑定到DataGridView,进一步优化UI性能:
// 定义ViewModel public class FolderViewModel { public int Index { get; set; } public string Path { get; set; } public int FileCount { get; set; } public string SizeMB { get; set; } public string CreatedDate { get; set; } } // 修改UI更新方法 private void loadFoldersToGrid(List<FolderViewModel> folderViewModels) { dgFolders.SuspendLayout(); // 需提前在设计时为DataGridView列绑定对应DataPropertyName dgFolders.DataSource = folderViewModels; dgFolders.ResumeLayout(); long totalSize = folderViewModels.Sum(vm => long.Parse(vm.SizeMB.Split(' ')[0])); txtFolderCount.Text = folderViewModels.Count.ToString(); txtSize.Text = string.Format("{0:N0} MB", totalSize); }
内容的提问来源于stack exchange,提问作者PCG
相关产品推荐
相关产品推荐

