使用文件流写入字节通过控制台输出进度时出现内存不足异常
本文关键字:输出 异常 内存不足 控制台 文件 字节 | 更新日期: 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次的报告进度对用户来说已经足够好了,控制台很容易跟上。