CsvHelper未按预期写入文件问题求助
解决CsvWriter中途终止导致文件为空的问题
这个问题我之前也碰到过!核心原因是StreamWriter和CsvWriter都依赖内存缓冲区——默认情况下,它们不会把每一条写入的数据立刻刷到磁盘,而是攒够一定量(或者等到流关闭时)才一次性写入。所以当你用Ctrl+C或者关闭窗口强制终止程序时,缓冲区里还没来得及写入磁盘的数据就丢了,最后文件自然是空的;而正常结束时,using块会自动调用Dispose()方法,这个方法会先把缓冲区的内容刷到磁盘再关闭流,所以数据能正常保存。
下面给你几个实用的解决方案:
方案1:每次写入后手动刷新缓冲区
在每次写完一条记录后,调用CsvWriter的Flush()方法,强制把缓冲区的数据写入磁盘。这样哪怕中途终止,之前写入的记录已经安全存在文件里了。
修改后的代码:
int i = 0; IList<string> pcodes = FindAutobidPcodes(); using (StreamWriter sw = new StreamWriter($"HPH_Catalog_{DateTime.Now.Ticks}.csv")) using (CsvWriter cw = new CsvWriter(sw, CultureInfo.InvariantCulture)) { cw.Configuration.RegisterClassMap<ProductMap>(); foreach (string code in pcodes) { // 修正了字符串插值里的大括号转义问题 string search = $"&search=%7b\"catalog\":\"{catalog}\",\"text\":{{\"{code}\":\"{code}\"}}}"; searchQuery.Query = $"{RandomString()}{limit}{page}{search}{gtemplate}&token={token}"; WebPage webPage = QueryProductData(searchQuery.ToString()); cw.WriteRecord(Product.ParseJSON(webPage)); cw.NextRecord(); // 手动刷新缓冲区,确保数据立即写入磁盘 cw.Flush(); i++; if(i == 20) { return; } } }
方案2:批量刷新,平衡性能和安全性
如果担心每次刷新会轻微影响性能(33k条数据其实完全不用担心,但追求最优的话),可以设置一个批次,比如每100条记录刷新一次,这样既减少IO操作次数,又能避免单次丢失太多数据:
int i = 0; IList<string> pcodes = FindAutobidPcodes(); using (StreamWriter sw = new StreamWriter($"HPH_Catalog_{DateTime.Now.Ticks}.csv")) using (CsvWriter cw = new CsvWriter(sw, CultureInfo.InvariantCulture)) { cw.Configuration.RegisterClassMap<ProductMap>(); foreach (string code in pcodes) { string search = $"&search=%7b\"catalog\":\"{catalog}\",\"text\":{{\"{code}\":\"{code}\"}}}"; searchQuery.Query = $"{RandomString()}{limit}{page}{search}{gtemplate}&token={token}"; WebPage webPage = QueryProductData(searchQuery.ToString()); cw.WriteRecord(Product.ParseJSON(webPage)); cw.NextRecord(); i++; // 每100条记录刷新一次缓冲区 if (i % 100 == 0) { cw.Flush(); } if(i == 20) { return; } } // 循环结束后再刷一次,确保最后一批不足100条的数据也写入磁盘 cw.Flush(); }
额外优化:监听强制终止信号(控制台应用)
如果你的程序是控制台应用,可以监听Ctrl+C的终止信号,在收到信号时手动执行一次Flush,最大程度减少数据丢失:
// 在程序初始化阶段注册终止信号处理逻辑 CsvWriter cw = null; Console.CancelKeyPress += (sender, e) => { // 确保刷新缓冲区 cw?.Flush(); // 延迟终止,保证Flush操作完成 e.Cancel = true; // 完成后退出程序 Task.Run(() => Environment.Exit(0)); }; // 后续的写入逻辑 int i = 0; IList<string> pcodes = FindAutobidPcodes(); using (StreamWriter sw = new StreamWriter($"HPH_Catalog_{DateTime.Now.Ticks}.csv")) { cw = new CsvWriter(sw, CultureInfo.InvariantCulture); cw.Configuration.RegisterClassMap<ProductMap>(); foreach (string code in pcodes) { string search = $"&search=%7b\"catalog\":\"{catalog}\",\"text\":{{\"{code}\":\"{code}\"}}}"; searchQuery.Query = $"{RandomString()}{limit}{page}{search}{gtemplate}&token={token}"; WebPage webPage = QueryProductData(searchQuery.ToString()); cw.WriteRecord(Product.ParseJSON(webPage)); cw.NextRecord(); i++; if(i == 20) { return; } } }
核心思路就是:不要只依赖using块的自动清理来处理异常或强制终止场景,主动控制缓冲区的刷新时机,确保数据及时落地到磁盘。
内容的提问来源于stack exchange,提问作者Jasin
相关产品推荐
相关产品推荐

