You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.02 18:36:02