C# Winform如何使用BackgroundWorker显示AES文件解密的真实进度
实现方案
你需要同时解决UI阻塞和进度计算两个需求,具体实现逻辑如下:
- 首先计算有效解密数据总长度:你加密的文件前32字节是固定的盐值,总密文长度 = 加密文件总长度 - 32字节,用这个值作为进度计算的分母。
- 每次从CryptoStream读取完缓冲区数据后,累加已读取的字节数,用
已处理字节数 / 有效密文总长度 * 100就可以得到实时的进度百分比。 - 移除原代码里的
Application.DoEvents(),这个方法会引发UI重入、重复触发操作等异常问题,改用BackgroundWorker或者IProgress回调更新UI,所有UI相关操作必须在主线程执行。
修改后的实现代码
首先在你的UI层初始化BackgroundWorker组件,启用进度报告:
// 窗体里声明BackgroundWorker private BackgroundWorker decryptWorker = new BackgroundWorker(); // 窗体初始化时配置 public Form1() { InitializeComponent(); decryptWorker.WorkerReportsProgress = true; decryptWorker.WorkerSupportsCancellation = true; decryptWorker.DoWork += DecryptWorker_DoWork; decryptWorker.ProgressChanged += DecryptWorker_ProgressChanged; decryptWorker.RunWorkerCompleted += DecryptWorker_RunWorkerCompleted; } // 进度更新回调,直接操作UI控件 private void DecryptWorker_ProgressChanged(object sender, ProgressChangedEventArgs e) { progressBar1.Value = e.ProgressPercentage; // 可以额外显示剩余时间,e.UserState可以传自定义数据比如已处理大小、速度等 } // 解密完成回调 private void DecryptWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { if (e.Error != null) { MessageBox.Show("解密失败:" + e.Error.Message); } else { MessageBox.Show("解密完成"); } progressBar1.Value = 0; } // 后台执行解密逻辑 private void DecryptWorker_DoWork(object sender, DoWorkEventArgs e) { object[] args = (object[])e.Argument; string inputFile = (string)args[0]; string password = (string)args[1]; FileDecrypt(inputFile, password, (BackgroundWorker)sender, e); } // 触发解密的按钮点击事件 private void btnDecrypt_Click(object sender, EventArgs e) { string inputFile = "你的加密文件路径"; string password = "你的解密密码"; decryptWorker.RunWorkerAsync(new object[] { inputFile, password }); }
然后修改你的FileDecrypt方法,加入进度计算逻辑:
public void FileDecrypt(string inputFile, string password, BackgroundWorker worker, DoWorkEventArgs e) { byte[] passwordBytes = System.Text.Encoding.UTF8.GetBytes(password); byte[] salt = new byte[32]; using (FileStream fsCrypt = new FileStream(inputFile, FileMode.Open)) { // 读取盐值 fsCrypt.Read(salt, 0, salt.Length); // 计算需要处理的有效密文总长度 long totalCipherLength = fsCrypt.Length - salt.Length; long processedBytes = 0; int lastReportedProgress = 0; RijndaelManaged AES = new RijndaelManaged(); AES.KeySize = 256; AES.BlockSize = 128; var key = new Rfc2898DeriveBytes(passwordBytes, salt, 50000); AES.Key = key.GetBytes(AES.KeySize / 8); AES.IV = key.GetBytes(AES.BlockSize / 8); AES.Padding = PaddingMode.PKCS7; AES.Mode = CipherMode.CFB; using (CryptoStream cs = new CryptoStream(fsCrypt, AES.CreateDecryptor(), CryptoStreamMode.Read)) { string getFilePath = Path.Combine(Path.GetDirectoryName(inputFile), Path.GetFileNameWithoutExtension(inputFile)); string fileDirectoryPath = Path.GetDirectoryName(getFilePath); string fileName = Path.GetFileNameWithoutExtension(getFilePath) + "_decrypted"; string fileExtenstion = Path.GetExtension(getFilePath); string finalFileNameFinal = Path.Combine(fileDirectoryPath, fileName + fileExtenstion); using (FileStream fsOut = new FileStream(finalFileNameFinal, FileMode.Create)) { int read; byte[] buffer = new byte[1048576]; // 1MB缓冲区,可以自己调整大小 try { while ((read = cs.Read(buffer, 0, buffer.Length)) > 0) { // 支持取消解密 if (worker.CancellationPending) { e.Cancel = true; break; } fsOut.Write(buffer, 0, read); processedBytes += read; // 计算进度,只有百分比变化时才上报,避免频繁更新UI导致卡顿 int currentProgress = (int)((double)processedBytes / totalCipherLength * 100); if (currentProgress > lastReportedProgress) { lastReportedProgress = currentProgress; worker.ReportProgress(currentProgress); } } } catch (CryptographicException ex_CryptographicException) { Console.WriteLine("CryptographicException error: " + ex_CryptographicException.Message); throw; // 把异常抛给RunWorkerCompleted处理 } catch (Exception ex) { Console.WriteLine("Error: " + ex.Message); throw; } } } } }
额外优化建议
- 如果要显示剩余时间,可以每次上报进度的时候同时记录当前时间和已处理字节数,计算处理速度,再用
(总长度 - 已处理长度) / 处理速度得到预估剩余时间,通过ReportProgress的第二个参数传到UI层显示。 - 所有文件流、加密对象都用using包裹,不用手动写Close和异常处理里的释放逻辑,代码更安全简洁。
内容的提问来源于stack exchange,提问作者Liryk
相关产品推荐
相关产品推荐

