[英]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.