[英]TPL Tasks + dynamic == OutOfMemoryException?
我正在开发一个流媒体推特客户端 - 经过1-2天的持续运行后,我的内存使用量超过了1.4gig(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(微软)的这个答案似乎表明,即使我的代码中没有任何引用,我也会在幕后解析对象,而这些对象并不会得到GC。
如果是这种情况,上面的代码中是否有某种方法可以阻止它或减少它?
我的后备是消除动态使用,但我不愿意。
更新:
12年12月14日:
答案 :
获得此特定示例以释放其任务的方法是产生(Thread.Sleep(0)),然后允许GC处理释放的任务。 我猜测在这种特殊情况下不允许处理消息/事件循环。
在我使用的实际代码 (TPL Dataflow)中, 我没有在块上调用Complete() ,因为它们是一个永无止境的数据流 - 只要twitter发送它们,任务就会使用Twitter消息。 在这个模型中,从来没有任何理由告诉任何,他们这样做,因为他们从来没有, 只要应用程序正在运行做了块。
不幸的是,它看起来并不像Dataflow块从未设计为长时间运行或处理无数项目,因为它们实际上保留了对发送到它们的所有内容的引用。 如果我错了,请告诉我。
因此,解决方法是定期(根据您的内存使用 - 我的每100k推特消息)释放块并重新设置它们。
根据这个方案,我的内存消耗永远不会超过80美分,在回收块并强制GC进行测量之后,gen2堆会回落到6megs,一切都很好。
12年10月17日:
与DLR相比,这更多地与远远领先于消费者的生产者有关。 循环尽可能快地创建任务 - 并且任务不会“立即”启动。 很容易弄清楚它可能落后多少:
int count = 0;
new Timer(_ => Console.WriteLine(count), 0, 0, 500);
while (true)
{
Interlocked.Increment(ref count);
Task.Factory.StartNew(() =>
{
dynamic dyn2 = new ExpandoObject();
dyn2.text = Get500kOfText() + Get500kOfText() + DateTime.Now.ToString() +
DateTime.Now.Millisecond.ToString();
Interlocked.Decrement(ref count);
});
}
输出:
324080
751802
1074713
1620403
1997559
2431238
这对于3秒钟的调度来说非常重要。 删除Task.Factory.StartNew
(单线程执行)会产生稳定的内存。
这里的问题不在于您正在创建的任务没有被清理。 Asti已经证明您的代码创建任务的速度比处理它们的速度快,所以当您清理已完成任务的内存时,最终仍会耗尽。
你说过:
在这个例子中放置战略性睡眠仍然会产生内存不足的例外 - 它只需要更长的时间
您尚未显示此代码或任何其他限制并发任务数的示例。 我的猜测是你在某种程度上限制了创造,但创造的速度仍然快于消费速度。 这是我自己的有限例子:
int numConcurrentActions = 100000;
BlockingCollection<Task> tasks = new BlockingCollection<Task>();
Action someAction = () =>
{
dynamic dyn = new System.Dynamic.ExpandoObject();
dyn.text = Get500kOfText() + Get500kOfText()
+ DateTime.Now.ToString() + DateTime.Now.Millisecond.ToString();
};
//add a fixed number of tasks
for (int i = 0; i < numConcurrentActions; i++)
{
tasks.Add(new Task(someAction));
}
//take a task out, set a continuation to add a new one when it finishes,
//and then start the task.
foreach (Task t in tasks.GetConsumingEnumerable())
{
t.ContinueWith(_ =>
{
tasks.Add(new Task(someAction));
});
t.Start();
}
此代码将确保一次不超过100,000个任务。 当我运行它时,内存是稳定的(平均时间为几秒)。 它通过创建固定数字来限制任务,然后设置延续以在现有任务完成时安排新任务。
所以这告诉我们的是,由于您的实际数据基于来自某些外部源的提要,因此您从该提要中获取数据的速度比处理它的速度快得多。 你有几个选择。 您可以在项目进入时对项目进行排队,确保当前只能运行有限数量的项目,并在超出容量时抛出请求(或者找到一些其他方式过滤输入以便您不进行全部处理) ,或者您可以获得更好的硬件(或优化您拥有的处理方法),以便您能够比创建请求更快地处理请求。
虽然通常我会说当人们已经“足够快”地运行时,人们倾向于尝试优化代码,但事实上并非如此。 你有一个相当硬的基准,你需要击中; 你需要比他们进来更快地处理项目。目前你没有达到那个基准(但是因为它在失败之前运行了一段时间,你不应该那么遥远)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.