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"); }
方案验证与优化建议
当前异步实现的问题点
- 资源泄漏风险:未使用
using包裹AesManaged、FileStream、CryptoStream,异常场景下无法保证资源正确释放,会导致文件句柄或内存泄漏。 - 加密数据不完整:
CopyToAsync完成后未调用FlushFinalBlockAsync(),加密缓冲区的最后一块数据无法写入,会导致加密文件损坏。 - 异常处理缺失:没有捕获IO、加密类异常,出错时会直接抛出未捕获异常导致程序崩溃,且不会清理不完整的目标文件。
- 主线程不必要切换:默认
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");
更优方案补充
- 替换AesManaged为Aes.Create():
Aes.Create()会返回平台最优的AES实现,兼容性和性能优于旧的AesManaged。 - 分块处理超大文件:对于GB级文件,可分块异步读写、加密/解密,降低内存占用。
- 添加进度回调:通过
IProgress<long>参数报告处理字节数,实现UI进度展示。 - 线程池调度:若无需访问Unity API,可使用
Task.Run将加密逻辑放到线程池执行,但注意Debug.Log等Unity API需切换回主线程调用。
内容的提问来源于stack exchange,提问作者Guri
相关产品推荐
相关产品推荐

