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

Unity大文件加密解密异步执行问题:Coroutine卡顿解决方案

Unity大文件异步加密解密方案验证与优化建议

问题背景

我正在开发一个Unity项目,需要对下载的文件进行加密解密,其中部分文件体积较大,必须实现异步执行避免整机卡顿、任务停滞。原本用Coroutine实现的EncryptFile和DecryptFile方法,因为同步文件拷贝操作导致主线程冻结,无法在后台执行。尝试过用async关键字和CopyToAsync方法,但一开始不知道怎么在Unity环境适配,现在已经实现了基于async/await的EncryptFileAndDeleteOriginal方法,想确认这个实现是否正确,或者有没有更优的Unity异步加密解密方案。


原调用代码

string encryptedFilePath = Path.Combine(FolderPath, SessionData.instance.email + "/", download.FileName + ".mp4");
yield return StartCoroutine(DownloadManagementPage.instance.EncryptFile(originalFile, encryptedFilePath));
DownloadManagementPage.instance.DeleteFile("TEMP.mp4");

原Coroutine加密解密方法

public IEnumerator EncryptFile(string srcFilename, string destFilename)
{
    Debug.LogError("Encrypting");
    Debug.LogError("Source " + srcFilename);
    Debug.LogError("Destination " + destFilename);
    var aes = new AesManaged();
    aes.BlockSize = aes.LegalBlockSizes[0].MaxSize;
    aes.KeySize = aes.LegalKeySizes[0].MaxSize;
    byte[] salt = Encoding.ASCII.GetBytes(SaltKey);
    var key = new Rfc2898DeriveBytes(SKey, salt, Iterations);
    aes.Key = key.GetBytes(aes.KeySize / 8);
    aes.IV = key.GetBytes(aes.BlockSize / 8);
    aes.Mode = CipherMode.CBC;
    ICryptoTransform transform = aes.CreateEncryptor(aes.Key, aes.IV);

    using (var dest = new FileStream(destFilename, FileMode.CreateNew, FileAccess.Write, FileShare.None))
    {
        using (var cryptoStream = new CryptoStream(dest, transform, CryptoStreamMode.Write))
        {
            using (var source = new FileStream(srcFilename, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                source.CopyTo(cryptoStream);
            }
        }
    }

    yield return null;
    Debug.LogError("Done Encryption");
}

public IEnumerator DecryptFile(string srcFilename, string destFilename)
{
    Debug.LogError("Decrypting");
    Debug.LogError("Source " + srcFilename);
    Debug.LogError("Destination " + destFilename);
    var aes = new AesManaged();
    aes.BlockSize = aes.LegalBlockSizes[0].MaxSize;
    aes.KeySize = aes.LegalKeySizes[0].MaxSize;
    byte[] salt = Encoding.ASCII.GetBytes(SaltKey);
    var key = new Rfc2898DeriveBytes(SKey, salt, Iterations);
    aes.Key = key.GetBytes(aes.KeySize / 8);
    aes.IV = key.GetBytes(aes.BlockSize / 8);
    aes.Mode = CipherMode.CBC;
    ICryptoTransform transform = aes.CreateDecryptor(aes.Key, aes.IV);

    using (var dest = new FileStream(destFilename, FileMode.CreateNew, FileAccess.Write, FileShare.None))
    {
        using (var cryptoStream = new CryptoStream(dest, transform, CryptoStreamMode.Write))
        {
            try
            {
                using (var source = new FileStream(srcFilename, FileMode.Open, FileAccess.Read, FileShare.Read))
                {
                    source.CopyTo(cryptoStream);
                }
            }
            catch (CryptographicException exception)
            {
                throw new ApplicationException("Decryption failed.", exception);
            }
        }
    }

    yield return null;
    Debug.LogError("Done Decryption");
}

优化后的异步加密方法(原实现)

public async Task EncryptFileAndDeleteOriginal(string srcFilename, string destFilename)
{
    Debug.LogError("Encrypting");
    Debug.LogError("Source " + srcFilename);
    Debug.LogError("Destination " + destFilename);
    var aes = new AesManaged();
    aes.BlockSize = aes.LegalBlockSizes[0].MaxSize;
    aes.KeySize = aes.LegalKeySizes[0].MaxSize;
    byte[] salt = Encoding.ASCII.GetBytes(SaltKey);
    var key = new Rfc2898DeriveBytes(SKey, salt, Iterations);
    aes.Key = key.GetBytes(aes.KeySize / 8);
    aes.IV = key.GetBytes(aes.BlockSize / 8);
    aes.Mode = CipherMode.CBC;
    ICryptoTransform cryptoTransform = aes.CreateEncryptor(aes.Key, aes.IV);
    FileStream source = new FileStream(srcFilename, FileMode.Open, FileAccess.Read, FileShare.Read);
    FileStream dest = new FileStream(destFilename, FileMode.CreateNew, FileAccess.Write, FileShare.None);
    CryptoStream cryptoStream = new CryptoStream(dest, cryptoTransform, CryptoStreamMode.Write);
    Task task = source.CopyToAsync(cryptoStream);
    await task;
    source.Close();
    dest.Close();
    Debug.LogError("Finished Encryption");
    //DeleteFilePath(srcFilename);
    Debug.LogError("Deleted Original");
}

方案验证与优化建议

当前异步实现的问题点

  1. 资源泄漏风险:未使用using包裹AesManaged、FileStream、CryptoStream,异常场景下无法保证资源正确释放,会导致文件句柄或内存泄漏。
  2. 加密数据不完整:CopyToAsync完成后未调用FlushFinalBlockAsync(),加密缓冲区的最后一块数据无法写入,会导致加密文件损坏。
  3. 异常处理缺失:没有捕获IO、加密类异常,出错时会直接抛出未捕获异常导致程序崩溃,且不会清理不完整的目标文件。
  4. 主线程不必要切换:默认await会回到Unity主线程,加密解密操作无需访问Unity API,切换会额外消耗性能。

修正后的异步加密方法

public async Task EncryptFileAndDeleteOriginal(string srcFilename, string destFilename)
{
    Debug.LogError("Encrypting");
    Debug.LogError("Source " + srcFilename);
    Debug.LogError("Destination " + destFilename);

    // using自动释放AES资源
    using var aes = new AesManaged();
    aes.BlockSize = aes.LegalBlockSizes[0].MaxSize;
    aes.KeySize = aes.LegalKeySizes[0].MaxSize;
    byte[] salt = Encoding.ASCII.GetBytes(SaltKey);
    var key = new Rfc2898DeriveBytes(SKey, salt, Iterations);
    aes.Key = key.GetBytes(aes.KeySize / 8);
    aes.IV = key.GetBytes(aes.BlockSize / 8);
    aes.Mode = CipherMode.CBC;
    ICryptoTransform cryptoTransform = aes.CreateEncryptor(aes.Key, aes.IV);

    // using自动释放文件流与加密流
    using var source = new FileStream(srcFilename, FileMode.Open, FileAccess.Read, FileShare.Read);
    using var dest = new FileStream(destFilename, FileMode.CreateNew, FileAccess.Write, FileShare.None);
    using var cryptoStream = new CryptoStream(dest, cryptoTransform, CryptoStreamMode.Write);

    try
    {
        // 异步拷贝,不切换回主线程提升性能
        await source.CopyToAsync(cryptoStream).ConfigureAwait(false);
        // 确保最后一块加密数据写入文件
        await cryptoStream.FlushFinalBlockAsync().ConfigureAwait(false);
        
        Debug.LogError("Finished Encryption");
        // 删除原文件
        File.Delete(srcFilename);
        Debug.LogError("Deleted Original");
    }
    catch (IOException ioEx)
    {
        Debug.LogError($"File operation failed: {ioEx.Message}");
        // 清理不完整的加密文件
        if (File.Exists(destFilename)) File.Delete(destFilename);
        throw;
    }
    catch (CryptographicException cryptoEx)
    {
        Debug.LogError($"Encryption failed: {cryptoEx.Message}");
        if (File.Exists(destFilename)) File.Delete(destFilename);
        throw;
    }
}

对应的异步解密方法

public async Task DecryptFile(string srcFilename, string destFilename)
{
    Debug.LogError("Decrypting");
    Debug.LogError("Source " + srcFilename);
    Debug.LogError("Destination " + destFilename);

    using var aes = new AesManaged();
    aes.BlockSize = aes.LegalBlockSizes[0].MaxSize;
    aes.KeySize = aes.LegalKeySizes[0].MaxSize;
    byte[] salt = Encoding.ASCII.GetBytes(SaltKey);
    var key = new Rfc2898DeriveBytes(SKey, salt, Iterations);
    aes.Key = key.GetBytes(aes.KeySize / 8);
    aes.IV = key.GetBytes(aes.BlockSize / 8);
    aes.Mode = CipherMode.CBC;
    ICryptoTransform cryptoTransform = aes.CreateDecryptor(aes.Key, aes.IV);

    using var source = new FileStream(srcFilename, FileMode.Open, FileAccess.Read, FileShare.Read);
    using var dest = new FileStream(destFilename, FileMode.CreateNew, FileAccess.Write, FileShare.None);
    using var cryptoStream = new CryptoStream(dest, cryptoTransform, CryptoStreamMode.Write);

    try
    {
        await source.CopyToAsync(cryptoStream).ConfigureAwait(false);
        await cryptoStream.FlushFinalBlockAsync().ConfigureAwait(false);
        Debug.LogError("Done Decryption");
    }
    catch (CryptographicException exception)
    {
        Debug.LogError($"Decryption failed: {exception.Message}");
        if (File.Exists(destFilename)) File.Delete(destFilename);
        throw new ApplicationException("Decryption failed.", exception);
    }
    catch (IOException ioEx)
    {
        Debug.LogError($"File operation failed: {ioEx.Message}");
        if (File.Exists(destFilename)) File.Delete(destFilename);
        throw;
    }
}

Unity环境下的调用方式

Unity 2019.3+支持直接在Coroutine中等待Task,调用方式如下:

string encryptedFilePath = Path.Combine(FolderPath, SessionData.instance.email + "/", download.FileName + ".mp4");
// 将Task转为Coroutine等待完成
yield return DownloadManagementPage.instance.EncryptFileAndDeleteOriginal(originalFile, encryptedFilePath).AsCoroutine();
DownloadManagementPage.instance.DeleteFile("TEMP.mp4");

更优方案补充

  1. 替换AesManaged为Aes.Create():Aes.Create()会返回平台最优的AES实现,兼容性和性能优于旧的AesManaged。
  2. 分块处理超大文件:对于GB级文件,可分块异步读写、加密/解密,降低内存占用。
  3. 添加进度回调:通过IProgress<long>参数报告处理字节数,实现UI进度展示。
  4. 线程池调度:若无需访问Unity API,可使用Task.Run将加密逻辑放到线程池执行,但注意Debug.Log等Unity API需切换回主线程调用。

内容的提问来源于stack exchange,提问作者Guri

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 05:20:28