【发布时间】:2017-06-09 13:56:09
【问题描述】:
我正在尝试处理一些在阅读时会显着扩展的项目列表。我在异步函数中处理它们,所以在某些时候可能会发生太多函数运行并抛出 OutOfMemoryException。
我正在考虑根据一些硬限制来限制正在运行且未完成的同时任务的数量。但我无法真正预测有多少任务太少/太多,因为每个项目的扩展可能不同。
是否有一些聪明的方法可以在不引入硬限制的情况下避免异常?
引发行为的例子:
void Main()
{
Int32 million = 1000000;
List<Task> tasks = new List<Task>();
for (Int32 i = 0; i < 20; i++)
{
Task t = Do(million);
tasks.Add(t);
}
Task.WaitAll(tasks.ToArray());
}
public static async Task Do(Int32 count)
{
//I tried 'await Task.Yield()' here; didn't help.
//consume memory
StringBuilder dataBuilder = new StringBuilder();
for (Int32 i = 0; i < count; i++)
{
dataBuilder.Append(Guid.NewGuid().ToString());
}
String data = dataBuilder.ToString();
Console.WriteLine($"here [{data.Length}]");
//do I/O
await Task.Delay(10000);
}
【问题讨论】:
-
你为什么不用
Task.Run?这是编码的方式,您在屈服之前在原始线程中创建一个字符串。await不会通过魔法使任何东西异步运行,它只允许您使用await关键字来等待已经异步的操作 -
至于OOM异常,你是运行在32位还是64位?分配如此大的对象可能会在一段时间后导致内存碎片化,以至于分配器无法找到要使用的单个内存块。
-
更糟糕的是,通过逐位添加数据,StringBuilder 必须在每次填充前一个缓冲区时分配 new 缓冲区。重新分配是通过分配两倍于旧缓冲区的缓冲区并复制数据来执行的。这可能会为单个大字符串浪费 LOT 的内存。由于您已经知道最终字符串的大小,因此请在创建 SringBuilder 时指定它,例如
new StringBuilder(36*count); -
Task.Yield产量。它不安排任何运行。至于内存问题,您要分配 20 个字符串,长度为 36M 字节,每个字符串至少重新分配 10 次 - 这是很多字符串。 GC 没有机会运行,即使它运行了,它也无法足够快地清理那么多大对象。难怪你最终会出现 OOM。内存碎片足以造成这种情况 -
内存不足有两种解决办法:1.多买,2.少用。在这种情况下,我认为通过将算法重写为一次只作用于一小部分数据的流处理器,您可以获得更多的性能。典型的例子是一个字符串解析器,它一次只在内存中保存一个字符。
标签: c# asynchronous memory async-await out-of-memory