我正在开发一个流媒体推特客户端 - 经过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。
This answer from Eric Lippert (microsoft)似乎表明我正在幕后解析对象,即使我的代码中没有任何引用也没有得到任何参考,也不会得到GC。
如果是这种情况,上面的代码中是否有某种方法可以阻止或减少它?
我的后备是消除动态使用,但我不愿意。
更新
12年12月14日
答案:
获取此特定示例以释放其任务的方法是产生(Thread.Sleep(0)),然后允许GC处理释放的任务。我猜测在这种特殊情况下不允许处理消息/事件循环。
在我使用的实际代码(TPL Dataflow)中, 我没有在块上调用Complete() ,因为它们是作为一个永无止境的数据流 - 只要twitter发送它们,任务就会收到Twitter消息。在这个模型中,从来没有任何理由告诉任何阻止它们已经完成,因为只要应用程序正在运行,它们就不会 BE 完成。
不幸的是,看起来数据流块从未设计为长时间运行或处理无数项目,因为它们实际上保留了对发送到它们的所有内容的引用。如果我错了,请告诉我。
因此,解决方法是定期(根据您的内存使用量 - 我的每100k推特消息)释放块并重新设置它们。
根据这个方案,我的内存消耗量永远不会超过80美分,并且在回收块并强制GC进行测量后,gen2堆会回落到6megs,一切都很好。
12年10月17日
答案 0 :(得分:3)
与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
(单线程执行)会产生稳定的内存。
答案 1 :(得分:1)
这里的问题不在于您正在创建的任务未被清理。 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个任务。当我运行它时,内存是稳定的(平均时间为几秒)。它通过创建固定数字来限制任务,然后设置延续以在现有任务完成时安排新任务。
所以这告诉我们的是,由于您的真实数据基于来自某个外部来源的Feed,因此您从该Feed获取数据的速度要比处理它的速度快一些。你有几个选择。您可以在项目进入时对项目进行排队,确保当前只能运行有限数量的项目,并在超出容量时抛出请求(或者找到一些其他方式过滤输入以便您不进行全部处理) ,或者您可以获得更好的硬件(或优化您拥有的处理方法),以便您能够比创建请求更快地处理请求。
虽然通常我会说当人们已经“足够快”地运行时,人们倾向于尝试优化代码,但事实上并非如此。你有一个相当硬的基准,你需要击中;你需要比他们进来更快地处理项目。目前你没有达到那个基准(但是因为它在失败之前运行了一段时间,你不应该 远远的。)