使用Task.WhenAll批量解密Word文件时无法捕获异常求助
问题分析与解决方案
核心问题根源
你的批量解密异常无法捕获,本质原因是错误使用了ContinueWith:你添加到tasks列表的是ContinueWith返回的任务,而非原始的解密任务。当解密任务抛出异常时,ContinueWith默认会无视前置任务的失败状态继续执行,且自身不会传递前置任务的异常,导致Task.WhenAll认为所有任务都成功完成,自然无法触发catch块。
同时代码还存在其他隐患:跨线程操作UI、文件流未释放、异步事件处理不规范。
修正后的完整代码
GetPathOrExtention gte = new GetPathOrExtention(); // 优化解密方法:用using自动释放资源,避免文件锁定 private void DecryptDOCXFiles(string fileName, string password) { using (FileStream inputStream = new FileStream(fileName, FileMode.Open, FileAccess.Read)) using (WordDocument document = new WordDocument(inputStream, FormatType.Docx, password)) { // 用Path.Combine避免路径拼接错误 string outputPath = Path.Combine(gte.GetDirPath(fileName), gte.GetfileName(fileName).Replace("Encrypted-", "")); using (FileStream outputStream = new FileStream(outputPath, FileMode.Create, FileAccess.Write)) { document.Save(outputStream, FormatType.Docx); } } } private async Task DecryptWordFiles() { OpenFileDialog ofd = new OpenFileDialog(); ofd.Filter = "docx files (*.docx)|*.docx|All files (*.*)|*.*"; ofd.Multiselect = true; var tasks = new List<Task>(); // 提前校验密码是否为空 if (string.IsNullOrEmpty(TbDecPwd1.Text)) { MessageBox.Show("请输入解密密码!", "P-GEN", MessageBoxButtons.OK, MessageBoxIcon.Warning); return; } if (ofd.ShowDialog() != DialogResult.OK) return; // 初始化进度条 progressBar1.Visible = true; progressBar1.Maximum = ofd.FileNames.Length; progressBar1.Value = 0; LblProceeding.Text = "处理中...请勿关闭软件!"; string password = TbDecPwd1.Text; foreach (string file in ofd.FileNames) { if (Path.GetExtension(file).ToLower() != ".docx") { LblProceeding.ResetText(); MessageBox.Show("不支持该文件格式!\n\n仅支持Word 2019及以上版本的docx文件!", "P-GEN", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } // 直接收集原始解密任务,确保异常能被Task.WhenAll捕获 var decryptTask = Task.Run(() => DecryptDOCXFiles(file, password)); // 单独用ContinueWith处理进度更新,通过Invoke确保UI操作在主线程执行 decryptTask.ContinueWith(t => { if (this.InvokeRequired) this.Invoke((Action)(() => progressBar1.PerformStep())); else progressBar1.PerformStep(); }, TaskContinuationOptions.ExecuteSynchronously); tasks.Add(decryptTask); } try { await Task.WhenAll(tasks); LblProceeding.Text = "完成!"; MessageBox.Show("文件解密成功!", "P-GEN", MessageBoxButtons.OK, MessageBoxIcon.Information); } catch (AggregateException ex) { // 区分异常类型,给出精准提示 foreach (var innerEx in ex.InnerExceptions) { if (innerEx.Message.Contains("password")) { MessageBox.Show("密码错误!\n\n请确认密码正确,且文件未被其他软件打开!", "P-GEN", MessageBoxButtons.OK, MessageBoxIcon.Error); break; } else if (innerEx is IOException) { MessageBox.Show("文件被占用!\n\n请关闭正在打开的目标文件!", "P-GEN", MessageBoxButtons.OK, MessageBoxIcon.Error); break; } } } catch (Exception ex) { MessageBox.Show($"解密失败:{ex.Message}\n\n请确认密码正确,且文件未被其他软件打开!", "P-GEN", MessageBoxButtons.OK, MessageBoxIcon.Error); } finally { // 无论成功失败,都重置UI状态 LblProceeding.ResetText(); progressBar1.Visible = false; progressBar1.Value = 0; } } // 修改为async void,正确处理异步事件 private async void BtnWordFilesDec_Click(object sender, EventArgs e) { await DecryptWordFiles(); }
关键修改说明
- 异常捕获修复:将原始解密任务直接加入
tasks列表,而非ContinueWith的任务,确保Task.WhenAll能感知到解密任务的异常。 - 资源管理优化:用
using语句自动释放FileStream和WordDocument,避免文件被锁定或资源泄漏。 - 线程安全UI操作:通过
Invoke确保进度条更新在主线程执行,解决WinForms跨线程操作异常。 - 异步事件规范:将按钮点击事件改为
async void,正确等待异步任务完成,避免未捕获的后台异常。 - 用户体验优化:提前校验密码、区分异常类型给出精准提示、统一重置UI状态。
内容的提问来源于stack exchange,提问作者HB21
相关产品推荐
相关产品推荐

