使用文件流写入字节通过控制台输出进度时出现内存不足

我有以下代码在写大文件时抛出内存不足exception。 有什么我想念的吗?

我不知道为什么它会丢失内存错误,因为我认为Filestream最多只会使用4096个字节的缓冲区? 我不完全确定缓冲区的含义是否诚实,任何建议都会受到赞赏。

public static async Task CreateRandomFile(string pathway, int size, IProgress prog) { byte[] fileSize = new byte[size]; new Random().NextBytes(fileSize); await Task.Run(() => { using (FileStream fs = File.Create(pathway,4096)) { for (int i = 0; i < size; i++) { fs.WriteByte(fileSize[i]); prog.Report(i); } } } ); } public static void p_ProgressChanged(object sender, int e) { int pos = Console.CursorTop; Console.WriteLine("Progress Copied: " + e); Console.SetCursorPosition (0, pos); } public static void Main() { Console.WriteLine("Testing CopyLearning"); //CopyFile() Progress p = new Progress(); p.ProgressChanged += p_ProgressChanged; Task ta = CreateRandomFile(@"D:\Programming\Testing\RandomFile.asd", 99999999, p); ta.Wait(); } 

编辑:99,999,999刚刚创建为99MB文件

注意:我已经注释掉了prog.Report(i),它会正常工作。 似乎由于某种原因,错误发生在该行

 Console.writeline("Progress Copied: " + e); 

我不完全确定为什么会导致错误? 所以错误可能是因为progressEvent引起的?

编辑2:我已按照建议更改代码,以便使用以下内容每4000字节报告进度:

  if (i%4000==0) prog.Report(i); 

由于某些原因。 我现在能够写入高达900MB的文件。

我想问题是,为什么“编辑2”的代码允许它写入900MB就好了? 是因为它报告了进度并且写入控制台的次数比以前减少了4000倍? 我没有意识到控制台会占用这么多内存,特别是因为我假设所有它正在输出“进行复制”?

编辑3:

出于某种原因,当我更改以下行时,如下所示:

  for (int i = 0; i < size; i++) { fs.WriteByte(fileSize[i]); Console.Writeline(i) prog.Report(i); } 

在prog.Report(i)之前有一个“Console.Writeline()”,它可以正常工作并复制文件,虽然需要很长时间才能完成。 这让我相信这是一个与控制台相关的问题,但由于某种原因,我不确定是什么。

  fs.WriteByte(fileSize[i]); prog.Report(i); 

你创造了一个火管问题 。 在死锁和线程竞争之后,可能是线程引起的第三个最可能的问题。 同样难以诊断。

通过使用调试器的Debug + Windows + Threads窗口最简单地查看并查看正在执行CreateRandomFile()的线程。 运气好的话,你会看到它已经完成并且写了所有99MB字节。 但是在控制台上报告的进度远远落后于此,仅报告了125KB字节的写入,给予或接受。

核心问题是Progress <>。Report()的工作方式。 它使用SynchronizationContext.Post()来调用ProgressChanged事件处理程序。 在将调用ThreadPool.QueueUserWorkItem()的控制台模式应用程序中。 这很快,你的CreateRandomFile()方法不会因此而陷入困境。

但是事件处理程序本身要慢很多,控制台输出不是很快。 因此,实际上,您正在以极快的速度添加线程池工作请求,其中9900万个在几秒钟内完成。 线程池调度程序无法跟上,你将大约有4个同时执行。 所有人都竞争写入控制台,其中只有一个可以获得底层锁。

因此,线程池调度程序导致OOM,被迫存储这么多工作请求。

当然,当你不频繁地调用Report()时,火管问题就更糟了。 实际上并不是那么简单,以确保它永远不会导致问题,虽然直接调用Console.Write()是一个明显的修复。 最终简单,创建一个对人类有用的可用UI。 没有人喜欢疯狂的滚动窗口或模糊的文字。 报告进度不超过每秒20次,对用户来说足够好,控制台可以毫不费力地跟上它。