2010-03-22 44 views
4

問題

我一直在努力解決這個問題兩天,現在只是用完了想法。有點...背景:我們有一個WinForms應用程序需要訪問數據庫,根據這些數據構造一個相關的內存對象列表,然後顯示在DataGridView上。重要的一點是,我們首先填充一個應用程序範圍的緩存(List),然後爲DGV所在的表單創建一個本地緩存的鏡像(使用List構造函數參數)。C#:BackgroundWorker克隆資源?

因爲讀取數據需要幾秒鐘的時間(DB位於局域網服務器上)加載,所以我們決定使用BackgroundWorker,並且只在數據加載後刷新DGV。但是,似乎通過BGW進行加載會導致內存泄漏......或者我的錯誤。當使用阻塞方法調用加載時,該應用程序消耗大約30MB的RAM;與BGW這跳躍到80MB!雖然看起來可能不是那麼多,但我們的客戶並不太高興。

相關代碼

private void MyForm_Load(object sender, EventArgs e) 
{ 
    MyRepository.Instance.FinishedEvent += RefreshCache; 
} 
private void RefreshCache(object sender, EventArgs e) 
{ 
    dgvProducts.DataSource = new List<MyDataObj>(MyRepository.Products); 
} 

private static List<MyDataObj> Products { get; set; } 
public event EventHandler ProductsLoaded; 

public void GetProductsSync() 
{ 
    List<MyDataObj> p; 

    using (MyL2SDb db = new MyL2SDb(MyConfig.ConnectionString)) 
    { 
     p = db.PRODUCTS 
     .Select(p => new MyDataObj {Id = p.ID, Description = p.DESCR}) 
     .ToList(); 
    } 

    Products = p; 

    // tell the form to refresh UI 
    if (ProductsLoaded != null) 
     ProductsLoaded(this, null); 

} 

public void GetProductsAsync() 
{ 
    using (BackgroundWorker myWorker = new BackgroundWorker()) 
    { 
     myWorker.DoWork += delegate 
     { 
      List<MyDataObj> p; 
      using (MyL2SDb db = new MyL2SDb(MyConfig.ConnectionString)) 
      { 
       p = db.PRODUCTS 
       .Select(p => new MyDataObj {Id = p.ID, Description = p.DESCR}) 
       .ToList(); 
      } 

      Products = p; 
     }; 

     // tell the form to refresh UI when finished 
     myWorker.RunWorkerCompleted += GetProductsCompleted; 
     myWorker.RunWorkerAsync(); 
    } 
} 

private void GetProductsCompleted(object sender, RunWorkerCompletedEventArgs e) 
{ 
    if (ProductsLoaded != null) 
     ProductsLoaded(this, null); 
} 

結束!

GetProductsSync或GetProductsAsync在主線程上調用,上面沒有顯示。難道是GarbageCollector會因爲兩個線程而丟失?或者是任務管理器顯示不正確的值?

對任何迴應,建議,批評都會大作讚揚。

+1

你處置BackgroundWorker的,而它仍然工作。你不應該在那裏使用'using'語句。 – SLaks 2010-03-22 14:19:49

+0

是的,Slaks是對的。放棄在Bgw周圍使用。 – 2010-03-22 14:21:53

+0

你在什麼時候分離RunWorkerComplete事件? – Kilhoffer 2010-03-22 14:22:19

回答

1

有趣的是 - 遵循Henk的建議,並使用真正的分析器(.Net內存分析器)而不是任務管理器。

雖然MEM使用數字幾乎是相同的,MyDataObj情況下,在兩個同步和異步的情況下,虛擬內存和堆的大小也很接近......還是什麼好奇等於預期(DB)的預期數量是怎麼回事上。由於ntdll調用VirtualAlloc(),總會有1.5MB的差異。其中大約1MB來自DllUnregisterServerInternal(),它在異步情況下佔用18.7MB(對比17.7MB)。其餘大部分來自CoUninitializeEE(),它在異步版本中被調用,但不被同步應用程序(?)調用。我知道,這是深挖泥土 - 道歉。上述1.5MB是我能找到的唯一真正的區別 - 只是我猜測它可能是其他事情正在發生的標誌。

真正的問題是:爲什麼任務管理器顯示大量不同的數字?它不能很好地處理BackgroundWorkers嗎?你有沒有遇到過如此巨大的差異(30MB vs 80MB)?

+1

Dav,就TaskManager測量內存使用量爲50MB而言,這是一個很小的差異。但在現實世界中,它也很小,而現代操作系統下的內存使用情況非常複雜。 – 2010-03-22 17:43:32

+0

好吧,我猜不應該把它「巨大差異」 - 我的意思是80MB是30MB的兩倍多,而不是一個消耗80MB的應用程序是資源。也許你是對的 - 再次,我們有責任向客戶介紹受管理環境中的資源消耗估算。感謝您的所有輸入Henk,並保持冷靜。 (仍然願意把它提交給MS任務經理團隊,但這是另一回事!) – Dav 2010-03-22 23:17:22

0

我不能完全肯定這是否會有所幫助,但在異步方法,你可以改變這一點:

List<MyDataObj> p; 
using (MyL2SDb db = new MyL2SDb(MyConfig.ConnectionString)) 
{ 
    p = db.PRODUCTS 
    .Select(p => new MyDataObj {Id = p.ID, Description = p.DESCR}) 
    .ToList(); 
} 

Products = p; 

這樣:

using (MyL2SDb db = new MyL2SDb(MyConfig.ConnectionString)) 
{ 
    Products = db.PRODUCTS 
    .Select(p => new MyDataObj {Id = p.ID, Description = p.DESCR}) 
    .ToList(); 
} 

我不認爲你需要的那裏有額外的列表變量。這可能是爲什麼?你正在創建一個額外的列表?無論哪種方式,這也是一個更清潔的外觀:)

+1

這不能造成問題的原因,這是一個額外的參考同一個列表? ,並不複製任何對象。 – SLaks 2010-03-22 14:28:13

+0

嗯......我只是意識到你正在做的事情他在同步代碼中是同樣的東西......所以可能不是這樣。但是也許可以改變代碼來查看它是否對兩者都有幫助? – 2010-03-22 14:29:16

+0

感謝您的意見,但傾向於贊同SLaks,這絕對不是問題。我更喜歡保留一個局部變量,在使用中分配它,並且在使用之外做其他需要做的事情。這樣我沒有例如。裏面有一個回報,imho使得更多的可讀/可理解的代碼成爲可能。但這只是個人選擇:-) – Dav 2010-03-22 14:55:33