簡體   English   中英

TPL任務+動態== OutOfMemoryException?

[英]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日:

  • “這沒有做任何有用的事情” :這個例子只是為了讓你快速生成問題。 它是從幾百行代碼中解決的,與代碼無關。
  • 無限循環創建任務並反過來創建對象 ”:記住 - 這只是快速演示問題 - 實際代碼坐在那里等待更多流數據。 另外 - 查看代碼 - 所有對象都在任務中的Action <> lambda內創建。 它超出范圍后為什么不被清理(最終)? 這個問題也不是因為做得太快 - 實際的代碼需要超過一天才能達到內存不足的例外 - 這只是讓它足夠快速地嘗試。
  • “保證任務被釋放嗎?” 對象是一個對象,不是嗎? 我的理解是,調度程序只是在池中使用線程,而它正在執行的lambda將在完成運行后被拋棄,無論如何。

與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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM