读取Zip文件时进程无异常突然退出,finally块未执行问题排查
我尝试从SFTP服务器读取一系列Zip文件,每个文件中包含一个固定宽度的数据文件。将这些文件下载到临时文件后尝试解压,但最后一步始终无法成功:进程没有报错、没有日志输出,直接突然退出,绕过了try...catch...finally块。已尝试使用多个不同的Zip库,但均出现相同行为。临时文件可通过Windows资源管理器正常打开。
相关C#代码如下:
public async Task ProcessFiles(CommandLineOptions options) { try { var privateKey = new PrivateKeyFile(options.PrivateKeyPath, options.PrivateKeyPassword); _logger.LogDebug("Loaded up the private key for {username}.", options.LoginUsername); var connection = new ConnectionInfo(options.Host, options.LoginUsername, new PrivateKeyAuthenticationMethod(options.LoginUsername, privateKey)); _logger.LogDebug("Created a connection object for SFTP to {host}.", options.Host); using var client = new SftpClient(connection); client.Connect(); _logger.LogDebug("Connected to {host}.", options.Host); var files = client.ListDirectory(".").ToList(); _logger.LogTrace("{numFiles} file(s) found in the directory.", files.Count); foreach (var file in files.Where(fn => fn.FullName.EndsWith(".zip", StringComparison.InvariantCultureIgnoreCase))) { string localFile = null; try { _logger.LogDebug("Processing file {fileName}", file.FullName); localFile = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid():N}.ZIP"); _logger.LogTrace("Writing to {tempPath}", localFile); await using var downStream = File.OpenWrite(localFile); client.DownloadFile(file.Name, downStream); _logger.LogDebug("File {fileName} downloaded.", localFile); await downStream.DisposeAsync(); _logger.LogTrace("Opening {fileName} for read access...", localFile); await using var upStream = File.Open(localFile,FileMode.OpenOrCreate); _logger.LogTrace("File has been opened for streaming, reading in ZipArchive..."); var archive = ZipArchive.Open(upStream, new ReaderOptions { LeaveStreamOpen = true, LookForHeader = true, DisableCheckIncomplete = false }); _logger.LogTrace("Archive file successfully opened."); var reader = archive.ExtractAllEntries(); foreach (var entry in archive.Entries) { _logger.LogTrace("Opening file {compressedFile} in the ZIP file {fileName}...", entry.Key, file.FullName); await using var inStream = reader.OpenEntryStream(); foreach (var record in _recordLoader.GetRecords(inStream)) { _logger.LogTrace("Record parsed for file type {type} sequence {sequence}...", record.Type, record.SequenceNumber); record.TenantId = options.TenantId; await _dbContext.Trades.AddAsync(record); _logger.LogTrace("Adding trade {sequence} in file {zippedFile} to Trades...", record.SequenceNumber, entry.Key); } var added = await _dbContext.SaveChangesAsync(); _logger.LogInformation("Loaded {recordsAdded} records from the zipped file {zippedFile}.", added, entry.Key); } } catch (Exception ex) { _logger.LogError(ex, "Failure downloading file {fileName}.", file.FullName); } finally { _logger.LogDebug("Deleting local file {localFile}", localFile); if (File.Exists(localFile)) File.Delete(localFile); _logger.LogDebug("File {localFile} deleted."); } } _logger.LogInformation("Pershing file processing is complete."); } catch (Exception ex) { _logger.LogError(ex, "Process to load Pershing files failed. The exception is included."); } }
运行日志如下:
dbug: Microsoft.EntityFrameworkCore.Infrastructure[10401] An 'IServiceProvider' was created for internal use by Entity Framework. dbug: AppRunner[0] Loaded up the private key for mycompanysftp.pershing.importer. dbug: AppRunner[0] Created a connection object for SFTP to mycompanysftp.blob.core.windows.net. dbug: AppRunner[0] Connected to mycompanysftp.blob.core.windows.net. trce: AppRunner[0] 5 file(s) found in the directory. dbug: AppRunner[0] Processing file /GSDE20221011.ZIP trce: AppRunner[0] Writing to C:\Users\jholo\AppData\Local\Temp\10826b52fba94b7db7844958dd3c2e9e.ZIP dbug: AppRunner[0] File C:\Users\jholo\AppData\Local\Temp\10826b52fba94b7db7844958dd3c2e9e.ZIP downloaded. trce: AppRunner[0] Opening C:\Users\jholo\AppData\Local\Temp\10826b52fba94b7db7844958dd3c2e9e.ZIP for read access... trce: AppRunner[0] File has been opened for streaming, reading in ZipArchive... D:\Repos\jholovacs\mycompany\mycompany.Data.Pershing.TestApp\bin\Debug\net6.0\mycompany.Data.Pershing.Importer.exe (process 50628) exited with code 0.
请问这是什么原因?为何finally块会被绕过?为何没有抛出异常?我哪里操作错了?
核心原因
进程无预警退出且跳过异常处理逻辑,大概率是非托管代码抛出的致命异常(如AccessViolationException),这类异常不受CLR托管异常机制控制,会直接终止进程,不会进入try/catch/finally块。结合你的场景,触发点集中在Zip文件读取阶段,可能和文件流状态异常、Zip格式兼容问题有关。
具体修复步骤
1. 修正文件流操作的错误
(1) 确保下载文件完全写入磁盘
client.DownloadFile是同步方法,写入流后需强制刷新缓冲区,再释放流,避免文件未完全写入就被读取:
client.DownloadFile(file.Name, downStream); downStream.Flush(); // 强制将缓冲区内容写入磁盘 await downStream.DisposeAsync();
(2) 正确打开Zip文件流
文件已下载完成,无需使用FileMode.OpenOrCreate,改用FileMode.Open并指定只读权限,避免流状态异常:
await using var upStream = File.Open(localFile, FileMode.Open, FileAccess.Read);
(3) 正确释放ZipArchive资源
ZipArchive实现了IDisposable,需用using包裹,确保非托管资源被正确释放:
using var archive = ZipArchive.Open(upStream, new ReaderOptions { LeaveStreamOpen = true, LookForHeader = true, DisableCheckIncomplete = false });
2. 确保异步任务被完整等待
如果是控制台程序,主入口必须等待ProcessFiles任务完成,否则进程会提前退出:
static async Task Main(string[] args) { var options = ParseCommandLineOptions(args); var appRunner = new AppRunner(); await appRunner.ProcessFiles(options); }
3. 调试捕获致命异常
添加全局异常处理,捕获非托管致命异常:
// 在程序入口处添加 AppDomain.CurrentDomain.UnhandledException += (sender, e) => { var ex = e.ExceptionObject as Exception; _logger.LogCritical(ex, "未处理的致命异常,进程将终止"); Console.WriteLine($"致命异常:{ex?.Message}"); };
同时在Visual Studio调试时,开启捕获所有异常(调试→窗口→异常设置,勾选"Common Language Runtime Exceptions"下的所有选项),可直接定位触发进程终止的异常点。
内容的提问来源于stack exchange,提问作者user610217

