2011-10-19 61 views
2

我寫了我的第一個MVVM應用程序。當我關閉應用程序時,我經常因ObjectDisposedException而導致崩潰。當應用程序窗口消失後,應用程序就會死機。獲取堆棧跟蹤很困難(see my other question),但最後我做了,並發現我的堆棧跟蹤完全包含在C#庫(kernel32!BaseThreadStart,mscorwks!Thread,mscorwks!WKS等)中。如何解決我的C#/ MVVM應用程序中無法解析的ObjectDisposedExceptions?

此外,這次崩潰是不一致的。在我上次結帳並重建之後,它停止了一段時間...。然後它回來了。一旦開始發生,即使我「清理」並重建,它也會繼續發生,。但擦拭和結賬有時會讓它停一會兒。

什麼,我認爲正在發生的事情:

我覺得我的處置時的ViewModels的GarbageCollector做一些有趣的事情。我的ViewModelBase類析構函數有一個WriteLine來記錄何時調用析構函數,而我的4個ViewModel中只有2或3個被處理,而且它看起來根據結帳而變化(例如,當我運行它時,我看到一個一致的重複序列,但我的同事看到不同的序列與不同的對象處理)。

由於stacktrace中沒有我的代碼調用,所以我認爲這意味着它不是調用已處理對象的方法的代碼我的代碼。所以這讓我認爲CLR是愚蠢的。

這是否有意義?有什麼方法可以讓GC保持一致?這是紅鯡魚嗎?在我App.xaml.cs文件的應用程序的啓動事件處理程序創建
我所有的意見和的ViewModels的:

,可以幫助其他細節。同一個處理程序將ViewModels分配給DataContext。我不確定這是否是正確的MVVM練習(正如我所說,我的第一個MVVM應用程序),但我不明白爲什麼它會導致不良行爲。

如果需要,我可以粘貼代碼。

+2

爲什麼你的ViewModel首先有終結器?你應該*幾乎不需要*終結者。 –

+0

這是從我學習MVVM的文章中的一個類。終結者除了登錄行之外什麼都不做,所以我不認爲它是有害的。你認爲這是造成這次事故嗎? –

+0

我懷疑它是*導致*崩潰(除非控制檯本身已被處置),但這通常是一個壞主意。值得注意的是,垃圾回收和調用Dispose是非常不同的東西。 –

回答

4

您的應用程序拋出異常,因爲您的主應用程序退出時,ViewModel銷燬的日誌記錄操作尚未完成。

你會發現,爲了執行實際的文件寫入,產生了一個子進程。如果在您的主應用程序退出時尚未完成,則會出現錯誤。

如果您要執行這種類型的操作,那麼您需要主應用程序等待任何子進程/線程池線程等的完成時間才能退出。

如果您希望確保您可以記錄應用程序關閉期間發生的事件,那麼我建議您將日誌記錄過程(實際寫入日誌文件)作爲您發佈消息的單獨主線程運行。這樣,在您的日誌記錄過程完成寫入磁盤之前,您的應用程序可以關閉。

+0

我不知道析構器是如此痛苦。擺脫它似乎解決了這個問題。謝謝。 –

13

我ViewModelBase類的析構擁有的WriteLine時調用析構函數來登錄,

這是非常糟糕的。我希望你只能在調試版本中啓用它。

你絕對不應該在之前在析構函數中做任何複雜的操作,比如創建文件句柄,操縱磁盤狀態等等。這只是要求最糟糕的一種麻煩。 析構函數應該清理一個非託管資源,除此之外別無其他。

我4周的ViewModels的

,只有2或3得到安置,而且似乎取決於結賬(如改變,當我在我的運行它,我看到一個始終重複序列,但我的同事看到了不同的序列用不同的物體處理)。

你在不同的時間看到事情以不同的順序發生是完全可以預料的,我們將在下面看到。

正確編寫析構函數是C#中最難的事情之一;在流程關閉之前的最後一輪定案中發現異常表明您可能做錯了。

因此,讓我想到CLR是愚蠢的。

爲您的錯誤指責工具不太可能幫助您解決問題。

事情大家寫任何的析構函數之前應瞭解的是:

  • 析構函數不一定在同一線程任何其他代碼上運行。這意味着您可能會遇到競爭狀況,鎖定順序問題,由於內存模型較弱等原因而及時移動讀取和寫入。 如果你使用析構函數,你會自動編寫一個多線程程序,因此你必須設計程序來抵禦所有可能的線程問題。這就是你的你的責任,而不是CLR的責任。如果你不願意承擔編寫線程安全對象的責任,那麼不要寫析構函數。

  • 即使對象從未初始化,析構函數也會運行。完全可能的是,在分配對象並且代碼在構造函數的中途,拋出異常。該對象被分配,你沒有壓制最終化,因此它必須被破壞。 析構函數需要在未完成初始化的對象時具有魯棒性。

  • 如果對象是旨在確保一致的突變鎖下,並拋出一個異常,並且finally塊不會恢復一致狀態,那麼對象將是不一致的狀態,一旦最後定稿。 由於事務中止,內置狀態不一致的對象需要析構函數強壯。

  • 破壞者可以運行在任何順序。如果你有一棵相互引用的對象樹,它們都是同時死的,那麼每個對象的析構函數都可以隨時運行。 對於內部狀態指向已經或尚未被破壞的其他對象的對象,析構函數必須是健壯的。

  • 根據垃圾回收器,等待終結器隊列銷燬的對象是alive析構函數會導致先前死亡的對象暫時(我們希望!)再次活躍起來。如果你的程序邏輯取決於死對象死亡,你必須非常小心你的析構函數。 (如果析構函數邏輯使物體永久再次活着,你可能有一個大問題,在您的手中,不要做。)

  • 因爲等待銷燬的對象是活着,他們被確定爲需要由於GC將它們歸類爲死亡,等待完成的對象在世代垃圾收集器中自動向上移動一代。這意味着垃圾收集器對存儲的回收不會發生,直到對象在第二次第二次時死亡。由於對象剛剛移動到後一代,因此很可能無法確定。 析構函數會導致短暫的內存分配變得壽命更長,這會在某些情況下嚴重影響垃圾回收器的性能。在爲大型短暫對象編寫析構函數之前要仔細考慮(或者更糟糕的是,您將創造數百萬的小型短暫對象); 具有析構函數的對象不能被gen收集器釋放,除非您明確禁止定義

  • 破壞者是不保證被調用。垃圾收集器是不需要在過程關閉之前運行對象的析構函數,即使它們已知死亡。您的邏輯不能取決於在調用析構函數時的正確性。許多事情可以防止析構函數被調用 - 例如FailFast或堆棧溢出異常,或者將電源線從牆上拔出。 面對不被調用的析構函數,程序必須是健壯的。

  • 引發未處理異常的析構函數使進程處於危險狀態。如果發生這種情況,運行時引擎完全有權對整個過程進行failfast。 (雖然需要才能這樣做。)析構函數絕對不能拋出未處理的異常。

如果你不願意與這些限制住那麼不寫在首位析構函數。無論你喜不喜歡,這些限制都不會消失。

+0

「破壞者導致短暫的內存分配變得壽命更長」 - 我從來不明白他們爲什麼以這種方式構建。收集器知道它只是叫做終結器,它不應該'升級'對象。如果終結器在下一個集合中仍然運行(或者對象再次活動),那麼它應該被升級 - 更有可能的是,該對象將在下一次收集,仍然在同一代。 – configurator

2

我認爲GarbageCollector在處理我的ViewModel時做了一些有趣的事情。我的ViewModelBase類析構函數有一個WriteLine來記錄何時調用析構函數

這可能是問題了。除非你真的有充分的理由這樣做,否則你不應該使用終結器,並且記錄的東西絕對不是其中之一。

您必須明白,終結器不會以可預測的順序運行。 GC可以按照需要的順序調用終結器,這可能解釋了爲什麼你會看到隨機的異常行爲。

相關問題