WinForms异步Task<bool>方法死锁问题求助(含进度条操作)
问题分析与解决方案
死锁根源
按钮点击事件里调用SaveBadCustomerMustStayMasdoodExcelFile(...).Result会强制阻塞UI线程,等待异步任务完成;而SendToDB方法中的_ProgressBar.Invoke需要UI线程空闲才能执行进度条更新。此时UI线程被.Result占着等异步任务,异步任务里的Invoke又等UI线程释放,形成循环等待,直接导致死锁。
正确修复方案
1. 改造按钮事件为异步方法
UI事件是少数适合用async void的场景,把按钮事件改成异步,用await替代.Result,彻底避免阻塞UI线程:
private async void btn_ChoiceBadCustomersFile_Click(object sender, EventArgs e) { try { DialogResult dialogResult = MessageBox.Show("message", "title", MessageBoxButtons.YesNo); if (dialogResult == DialogResult.Yes) { bool result = false; try { result = await GetDataFromExcelFile.SaveBadCustomerMustStayMasdoodExcelFile(progressBar_BadCustomers); } catch { result = false; } if (result) { // 操作成功后的业务逻辑 } } } catch { // 全局异常处理逻辑 } }
2. 优化进度条更新逻辑(推荐)
可以用两种方式避免进度条更新时的同步等待:
方式一:用BeginInvoke替代Invoke
BeginInvoke是异步调用UI线程,不会等待UI执行完成,直接继续后续业务逻辑:
// 在SendToDB方法中替换原有Invoke代码 _ProgressBar.BeginInvoke((MethodInvoker)(() => { _ProgressBar.Value = Convert.ToInt32(i); }));
方式二:用IProgress<T>优雅处理跨线程进度
这是.NET官方推荐的跨线程更新UI的方式,解耦业务逻辑和UI操作:
首先修改SaveBadCustomerMustStayMasdoodExcelFile方法参数:
public async static Task<bool> SaveBadCustomerMustStayMasdoodExcelFile(IProgress<int> progress) { try { PoomaDbAppEntities DB10 = new PoomaDbAppEntities(); IQueryable<tbl_BadCustomers> dt = null; MyExcelWorkSpace _excelApp = new MyExcelWorkSpace(); MyExcelWorkSpace.badCustomers badCustomers = new MyExcelWorkSpace.badCustomers(); string path = badCustomers.select(); if (path != String.Empty) { if (badCustomers.Open(path)) { try { await Task.Run(() => { dt = badCustomers.SendToDB(progress); }); return true; } catch { return false; } } else { return false; } } else { return false; } } catch { return false; } }
然后在按钮事件中创建Progress实例:
private async void btn_ChoiceBadCustomersFile_Click(object sender, EventArgs e) { try { DialogResult dialogResult = MessageBox.Show("message", "title", MessageBoxButtons.YesNo); if (dialogResult == DialogResult.Yes) { bool result = false; try { // 绑定进度条更新逻辑 var progress = new Progress<int>(value => { progressBar_BadCustomers.Value = value; }); result = await GetDataFromExcelFile.SaveBadCustomerMustStayMasdoodExcelFile(progress); } catch { result = false; } if (result) { // 操作成功后的业务逻辑 } } } catch { // 全局异常处理逻辑 } }
最后修改SendToDB方法:
public IQueryable<tbl_BadCustomers> SendToDB(IProgress<int> progress) { try { // 原有业务逻辑 if (progress != null) { // 上报进度 progress.Report(Convert.ToInt32(i)); } // 原有业务逻辑 } catch { // 异常处理逻辑 } return DB10.tbl_BadCustomers.Select(i => i); }
3. 保留Task<bool>返回值的必要性
绝对不要把SaveBadCustomerMustStayMasdoodExcelFile改成async void(UI事件除外):async void属于“一次性触发”的异步方法,调用方无法跟踪执行状态、获取返回值,也无法捕获其内部抛出的异常,只能看到程序“在跑”,但完全无法判断操作是否成功。
内容的提问来源于stack exchange,提问作者ferihfs
相关产品推荐
相关产品推荐

