我正在开发一个流式 Twitter 客户端 - 经过 1-2 天的持续运行后,我的内存使用量超过了 1.4gigs(32 位进程),并且在达到这个数量后不久,我就会内存不足本质上是这样的代码异常(此代码将在我的机器上的 <30 秒内出错):
while (true)
{
Task.Factory.StartNew(() =>
{
dynamic dyn2 = new ExpandoObject();
//get a ton of text, make the string random
//enough to be be interned, for the most part
dyn2.text = Get500kOfText() + Get500kOfText() + DateTime.Now.ToString() +
DateTime.Now.Millisecond.ToString();
});
}
我已经对其进行了分析,这绝对是由于 DLR 中的课程下降(从内存中 - 我在这里没有我的详细信息)xxRuntimeBinderxx 和 xxAggregatexx。
这个来自 Eric Lippert (microsoft) 的回答似乎表明我在幕后制作表达式解析对象,即使在我的代码中没有保留对任何内容的引用,也不会得到 GC。
如果是这种情况,上面的代码中是否有某种方法可以防止或减轻它?
我的后备是消除动态使用,但我不想这样做。
谢谢
更新:
2012 年 12 月 14 日:
答案:
让这个特定示例释放其任务的方法是 yield (Thread.Sleep(0)),这将允许 GC 处理释放的任务。我猜在这种特殊情况下不允许处理消息/事件循环。
在我使用的实际代码(TPL 数据流)中,我没有在块上调用 Complete(),因为它们是一个永无止境的数据流——只要 twitter 发送它们,该任务就会接收 Twitter 消息。在这个模型中,从来没有任何理由告诉任何块它们已经完成,因为只要应用程序正在运行,它们就永远不会被完成。
不幸的是,看起来 Dataflow 块从未被设计为长时间运行或处理无数项目,因为它们实际上保留了对发送到其中的所有内容的引用。如果我错了,请告诉我。
因此,解决方法是定期(根据您的内存使用情况——我的是每 10 万条推特消息)释放块并重新设置它们。
在这个方案下,我的内存消耗永远不会超过 80megs,并且在回收块并强制 GC 进行良好测量之后,gen2 堆又回到了 6megs,一切都恢复正常了。
2012 年 10 月 17 日:
- “这并没有做任何有用的事情”:这个例子只是为了让你快速生成问题。它是从与问题无关的几百行代码中总结出来的。
- “创建任务并反过来创建对象的无限循环”:请记住 - 这只是快速演示了问题 - 实际代码正坐在那里等待更多流数据。另外——查看代码——所有对象都是在任务中的 Action<> lambda 中创建的。为什么在超出范围后(最终)不对其进行清理?这个问题也不是因为做得太快——实际的代码需要一天多的时间才能到达内存不足的异常——这只是让它足够快地尝试一下。
- “任务能保证被释放吗?” 对象就是对象,不是吗?我的理解是调度程序只是在池中使用线程,并且它正在执行的 lambda 在它完成运行后无论如何都会被丢弃。