2015-10-06 57 views
10

我正在開發一個C#UWP 10解決方案,該解決方案使用快速,持續的讀/寫循環與網絡設備進行通信。由API提供的StreamSocket似乎工作得很好,直到我意識到存在內存泄漏:堆中有大約Task<uint32>的積累,每分鐘數百次。爲什麼在循環中使用StreamSocket會導致內存泄漏?

是否我使用普通老式while (true)環的async Task內部,或者使用自發布ActionBlock<T>與TPL數據流(根據this answer),其結果是一樣的。

我可以,如果我消除從套接字進行讀取來進一步隔離問題並專注於寫作: 無論我用DataWriter.StoreAsync方法或更直接的StreamSocket.OutputStream.WriteAsync(IBuffer buffer),問題依舊。此外,將這些.AsTask()添加到這些沒有任何區別。

即使垃圾收集器運行,這些Task<uint32>也不會從堆中移除。所有這些任務都已完成(RanToCompletion),沒有任何錯誤或任何其他屬性值可能表示「尚未完全準備好回收」。

this page(從託管世界到非託管世界阻止內存釋放的字節數組)似乎暗示了我的問題,但是規定的解決方案看起來很明顯:唯一的解決方法是編寫所有C++/CX中的通信邏輯。我希望這不是真的;當然,其他C#開發人員已經成功實現了無內存泄漏的持續高速網絡通信。而微軟肯定不會沒有內存泄漏在C++/CX

編輯

按照要求,一些示例代碼釋放只能的API。我自己的代碼層數太多,但使用this Microsoft sample可以觀察到更簡單的示例。我做了一個簡單的修改,在循環中發送1000次以突出問題。這是相關代碼:

public sealed partial class Scenario3 : Page 
{ 
    // some code omitted 

    private async void SendHello_Click(object sender, RoutedEventArgs e) 
    { 
     // some code omitted 

     StreamSocket socket = //get global object; socket is already connected 

     DataWriter writer = new DataWriter(socket.OutputStream); 

     for (int i = 0; i < 1000; i++) 
     { 
      string stringToSend = "Hello"; 
      writer.WriteUInt32(writer.MeasureString(stringToSend)); 
      writer.WriteString(stringToSend); 
      await writer.StoreAsync(); 
     } 
    } 
} 

在啓動應用程序和連接插口,還有的Task<UInt32>堆上唯一實例。點擊「SendHello」按鈕後,有86個實例。第二次按下後:129個實例。

編輯#2 運行我的應用程序之後(緊循環發送/接收)3小時,我可以看到,有絕對一個問題:50萬分任務的情況下,它從來沒有得到GC'd,應用的進程內存從最初的46 MB增加到了105 MB。很明顯,這個應用程序不能無限期地運行。 但是 ...這隻適用於運行在調試模式。如果我編譯我的應用程序版本模式,部署它並運行它,沒有內存問題。我可以讓它整夜運行,很明顯內存管理正常。 案件關閉。

+1

發佈代碼展示問題 – alexm

+0

@alexm,請參閱發佈的代碼。 Thx – BCA

+0

@BCA - 當您在事件處理程序中引用全局對象時,它如何能夠收集頁面? –

回答

10

有86個實例。第二次按下後:129個實例。

這完全正常。並且強烈暗示這裏真正的問題是您不知道如何正確解釋內存分析器報告。

任務聽起來像一個非常昂貴的對象,它有很多爆炸和一個線程涉及,你可以創建的最昂貴的操作系統對象。但事實並非如此,Task對象實際上是一個不起眼的對象。它只需要32位模式下的44個字節,64位模式下的80個字節。真正昂貴的資源不屬於任務,線程池管理器負責處理它。

這意味着在GC堆上施加足夠的壓力以觸發集合之前,您可以創建一個批次的Task對象。關於47千其中的32位模式填充gen#0段。更多的服務器上有成千上萬的服務器,其部分更大。

在您的代碼片段中,Task對象是您實際創建的唯一對象。因此你的for(;;)循環不會經常循環以至於看不到Task對象的數量減少或限制。

因此,這是通常的故事,對.NET Framework有泄漏的指責,特別是在服務器風格的應用程序運行數月之久的這些基本對象類型中,這種類型永遠是過分誇大的。雙重猜測垃圾回收器總是很棘手,你通常只會獲得信心,實際上你的應用程序運行了幾個月,並且從未在OOM上失敗。

+0

很有意思。我現在正在將我的實際應用程序用於試駕。一小時後,我有超過100,000個任務對象並計數。有趣的是,垃圾收集每隔幾秒就會發生,但這些Task對象永遠不會被清理。但是你說這是正常的,如果我不服氣,我應該讓我的應用程序運行,直到OOM。 – BCA

+0

您可以調用[GC.Collect](https://msdn.microsoft.com/en-us/library/xe0c2357(v = vs.110).aspx)強制垃圾回收來查看它是否清理了這些實例。如果確實如此,那麼我認爲你不必再爲此擔心。 – Damyan

0

我會在for內創建並關閉DataWriter。

+0

我做到了。每次迭代都涉及到套接字的使用塊 – BCA