简体   繁体   English

TPL任务+动态== OutOfMemoryException?

[英]TPL Tasks + dynamic == OutOfMemoryException?

I'm working on a streaming twitter client- after 1-2 days of constant running, I'm getting memory usage of >1.4gigs (32 bit process) and soon after it hits that amount, I'll get an out of memory exception on code that's essentially this (this code will error in <30 seconds on my machine): 我正在开发一个流媒体推特客户端 - 经过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(); 
  });
}

I've profiled it and it's definitely due to class way down in the DLR (from memory- I don't have my detailed info here) xxRuntimeBinderxx and xxAggregatexx. 我已经对它进行了分析,这肯定是由于DLR中的类向下(从内存 - 我这里没有我的详细信息)xxRuntimeBinderxx和xxAggregatexx。

This answer from Eric Lippert (microsoft) seems to indicate that I'm making expression parsing objects behind the scenes that don't ever get GC'd even though no reference is kept to anything in my code. Eric Lippert(微软)的这个答案似乎表明,即使我的代码中没有任何引用,我也会在幕后解析对象,而这些对象并不会得到GC。

If that's the case, is there some way in the code above to either prevent it or lessen it? 如果是这种情况,上面的代码中是否有某种方法可以阻止它或减少它?

My fallback is to eliminate the dynamic usage, but I'd prefer not to. 我的后备是消除动态使用,但我不愿意。

Thanks 谢谢

Update: 更新:

12/14/12: 12年12月14日:

THE ANSWER : 答案

The way to get this particular example to free up its tasks was to yield (Thread.Sleep(0)), which would then allow the GC to handle the freed up tasks. 获得此特定示例以释放其任务的方法是产生(Thread.Sleep(0)),然后允许GC处理释放的任务。 I'm guessing a message/event loop wasn't being allowed to process in this particular case. 我猜测在这种特殊情况下不允许处理消息/事件循环。

In the actual code I was using (TPL Dataflow), I was not calling Complete() on the blocks because they were meant to be a never-ending dataflow- the task would take Twitter messages as long as twitter would send them. 在我使用的实际代码 (TPL Dataflow)中, 我没有在块上调用Complete() ,因为它们是一个永无止境的数据流 - 只要twitter发送它们,任务就会使用Twitter消息。 In this model, there was never any reason to tell any of the blocks that they were done because they'd never BE done as long as the app was running. 在这个模型中,从来没有任何理由告诉任何,他们这样做,因为他们从来没有, 只要应用程序正在运行做了块。

Unfortunately, it doesn't look like Dataflow blocks were never designed to be very long running or handle untold numbers of items because they actually keep a reference to everything that's sent into them. 不幸的是,它看起来并不像Dataflow块从未设计为长时间运行或处理无数项目,因为它们实际上保留了对发送到它们的所有内容的引用。 If I'm wrong, please let me know. 如果我错了,请告诉我。

So the workaround is to periodically (based on your memory usage- mine was every 100k twitter messages) free the blocks and set them up again. 因此,解决方法是定期(根据您的内存使用 - 我的每100k推特消息)释放块并重新设置它们。

Under this scheme, my memory consumption never goes over 80megs and after recycling the blocks and forcing GC for good measure, the gen2 heap goes back down to 6megs and everything's fine again. 根据这个方案,我的内存消耗永远不会超过80美分,在回收块并强制GC进行测量之后,gen2堆会回落到6megs,一切都很好。

10/17/12: 12年10月17日:

  • "This isn't doing anything useful" : This example is merely to allow you to generate the problem quickly. “这没有做任何有用的事情” :这个例子只是为了让你快速生成问题。 It's boiled down from a few hundred lines of code that have nothing to do with the issue. 它是从几百行代码中解决的,与代码无关。
  • " An infinite loop creating a task and in turn creates objects ": Remember- this merely demonstrates the issue quickly- the actual code is sitting there waiting for more streaming data. 无限循环创建任务并反过来创建对象 ”:记住 - 这只是快速演示问题 - 实际代码坐在那里等待更多流数据。 Also- looking at the code- all of the objects are created inside the Action<> lambda in the task. 另外 - 查看代码 - 所有对象都在任务中的Action <> lambda内创建。 Why isn't this being cleaned up (eventually) after it goes out of scope? 它超出范围后为什么不被清理(最终)? The issue also isn't due to doing it too quickly- the actual code requires more than a day to arrive at the out of memory exception- this just makes it quick enough to try things out. 这个问题也不是因为做得太快 - 实际的代码需要超过一天才能达到内存不足的例外 - 这只是让它足够快速地尝试。
  • "Are tasks guaranteed to be freed?" “保证任务被释放吗?” An object's an object, isn't it? 对象是一个对象,不是吗? My understanding is that the scheduler's just using threads in a pool and the lambda that it's executing is going to be thrown away after it's done running regardless. 我的理解是,调度程序只是在池中使用线程,而它正在执行的lambda将在完成运行后被抛弃,无论如何。

This has more to do with the producer running far ahead of the consumer, than the DLR. 与DLR相比,这更多地与远远领先于消费者的生产者有关。 The loop creates tasks as fast as possible - and the tasks aren't started as "immediately". 循环尽可能快地创建任务 - 并且任务不会“立即”启动。 It's easy to figure out just how much it might lag behind: 很容易弄清楚它可能落后多少:

        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);
            });
        }

Output: 输出:

324080
751802
1074713
1620403
1997559
2431238

That's a lot for 3 seconds' worth of scheduling. 这对于3秒钟的调度来说非常重要。 Removing the Task.Factory.StartNew (single threaded execution) yields stable memory. 删除Task.Factory.StartNew (单线程执行)会产生稳定的内存。

The repro you've given seems a bit contrived, though. 不过,你给出的副本似乎有点做作。 If too many concurrent tasks is indeed your problem, you could try for a custom task scheduler that limits concurrent scheduling . 如果确实存在太多并发任务,则可以尝试使用限制并发调度自定义任务调度程序

The problem here is not that the tasks you are creating aren't being cleaned up. 这里的问题不在于您正在创建的任务没有被清理。 Asti has demonstrated that your code is creating tasks faster than they can be processed, so while you are clearing up the memory of completed tasks, you still run out eventually. Asti已经证明您的代码创建任务的速度比处理它们的速度快,所以当您清理已完成任务的内存时,最终仍会耗尽。

You have said: 你说过:

putting strategic sleeps in this example will still generate the out of memory exception- it'll just take longer 在这个例子中放置战略性睡眠仍然会产生内存不足的例外 - 它只需要更长的时间

You haven't shown the code for this, or any other example that bounds the number of concurrent tasks. 您尚未显示此代码或任何其他限制并发任务数的示例。 My guess is that you are limiting the creation to some degree, but that the rate of creation is still faster than the rate of consumption. 我的猜测是你在某种程度上限制了创造,但创造的速度仍然快于消费速度。 Here is my own bounded example: 这是我自己的有限例子:

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();
}

This code will ensure that no more than 100,000 tasks will be running at any one time. 此代码将确保一次不超过100,000个任务。 When I run this the memory is stable (when averaged over a number of seconds). 当我运行它时,内存是稳定的(平均时间为几秒)。 It bounds the tasks by creating a fixed number, and then setting a continuation to schedule a new task whenever an existing one finishes. 它通过创建固定数字来限制任务,然后设置延续以在现有任务完成时安排新任务。

So what this tells us is that since your real data is based on a feed from some external source you are getting data from that feed ever so slightly faster than you can process it. 所以这告诉我们的是,由于您的实际数据基于来自某些外部源的提要,因此您从该提要中获取数据的速度比处理它的速度快得多。 You have a few options here. 你有几个选择。 You could queue items as they come in, ensure that only a limited number can currently be running, and throw out requests if you have exceeded your capacity (or find some other way of filtering the input so that you don't process it all), or you could just get better hardware (or optimize the processing method that you have) so that you are able to process the requests faster than they can be created. 您可以在项目进入时对项目进行排队,确保当前只能运行有限数量的项目,并在超出容量时抛出请求(或者找到一些其他方式过滤输入以便您不进行全部处理) ,或者您可以获得更好的硬件(或优化您拥有的处理方法),以便您能够比创建请求更快地处理请求。

While normally I would say that people tend to try to optimize code when it already runs "fast enough", this clearly isn't the case for you. 虽然通常我会说当人们已经“足够快”地运行时,人们倾向于尝试优化代码,但事实上并非如此。 You have a fairly hard benchmark that you need to hit; 你有一个相当硬的基准,你需要击中; you need to process items faster than they come in. Currently you aren't meeting that benchmark (but since it runs for a while before failing, you shouldn't be that far off). 你需要比他们进来更快地处理项目。目前你没有达到那个基准(但是因为它在失败之前运行了一段时间,你不应该那么遥远)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM