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

本文关键字:输出 异常 内存不足 控制台 文件 字节 | 更新日期: 2023-09-27 18:20:34

我有以下代码,在编写大型文件时会引发内存不足异常。我有什么东西不见了吗?

我不知道为什么它会抛出内存不足错误,因为我认为Filestream最多只会使用4096字节的缓冲区?老实说,我不完全确定缓冲区意味着什么,任何建议都将不胜感激。

 public static async Task CreateRandomFile(string pathway, int size, IProgress<int> 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<int> p = new Progress<int>();
        p.ProgressChanged += p_ProgressChanged;
        Task ta = CreateRandomFile(@"D:'Programming'Testing'RandomFile.asd", 99999999, p);
        ta.Wait();
    }

编辑:创建99999999只是为了制作一个99MB的文件

注:我已经评论掉了prog。报告(i),它会很好地工作。似乎出于某种原因,错误发生在线上

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

我不完全确定为什么这会导致错误?那么这个错误可能是由progressEvent引起的?

编辑2:我遵循了修改代码的建议,使其通过使用以下代码每4000字节报告一次进度:

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

出于某种原因。我现在可以写高达900MB的文件了。

我想问题是,为什么"Edit 2"的代码允许它写入高达900MB的数据?是因为它报告的进度和对控制台的写入比以前减少了4000倍吗?我没有意识到控制台会占用这么多内存,尤其是因为我假设它所做的只是输出"复制的进度"?

编辑3:

出于某种原因,当我将以下行更改为:

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

其中程序前面有一个"Console.Writeline()"。报告(i),复制文件会很好,尽管需要很长时间。这让我相信这是一个与控制台相关的问题,但我不确定是什么。

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

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

您创建了消防软管问题。在死锁和线程竞争之后,可能是线程引起的第三大问题。同样难以诊断。

使用调试器的Debug+Windows+Threads窗口并查看正在执行CreateRandomFile()的线程,最容易看到。如果运气好的话,您会看到它已完成,并已写入所有99MB字节。但控制台上报告的进度远远落后于此,仅报告了125KB的写入字节数。

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

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

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

当然,当你调用Report()的频率降低时,消防软管的问题就不会那么严重了。尽管直接调用Console.Write()是一个明显的解决方案,但确保永远不会导致问题并不是那么简单。最终很简单,创建一个对人类有用的可用UI。没有人喜欢疯狂滚动的窗口或模糊的文本。每秒不超过20次的报告进度对用户来说已经足够好了,控制台很容易跟上。