質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

.NET Framework

.NET Framework は、Microsoft Windowsのオペレーティングシステムのために開発されたソフトウェア開発環境/実行環境です。多くのプログラミング言語をサポートしています。

WPF

Windows Presentation Foundation (WPF) は、魅力的な外観のユーザー エクスペリエンスを持つ Windows クライアント アプリケーションを作成するための次世代プレゼンテーション システムです

Q&A

解決済

4回答

25250閲覧

【WPF】ウィンドウを閉じてもメモリ上から破棄されない

twyujiro15

総合スコア217

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

.NET Framework

.NET Framework は、Microsoft Windowsのオペレーティングシステムのために開発されたソフトウェア開発環境/実行環境です。多くのプログラミング言語をサポートしています。

WPF

Windows Presentation Foundation (WPF) は、魅力的な外観のユーザー エクスペリエンスを持つ Windows クライアント アプリケーションを作成するための次世代プレゼンテーション システムです

5グッド

2クリップ

投稿2019/03/20 04:27

編集2019/03/22 08:11

開発環境

Visual Studio 2013、.NET Framework 4.6 で WPF アプリの開発をしています。

質問内容

下記のようなコードを Release ビルドし、
exe から単体で起動したところ、
ウィンドウを閉じた後に GC をしているにも関わらず、
ウィンドウの生存が確認されました。

最新の動作確認用コードは一番下にあります。
WM_DESTROY を処理しているにも関わらず GC で回収されないことがあります。

XAML

1<!-- App.xaml --> 2<!-- StartupUri プロパティの指定を削除しただけです --> 3<Application x:Class="WpfApplication3.App" 4 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 5 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> 6 <Application.Resources> 7 8 </Application.Resources> 9</Application>

C#

1// App.xaml.cs 2namespace WpfApplication3 3{ 4 using System; 5 using System.Windows; 6 7 /// <summary> 8 /// App.xaml の相互作用ロジック 9 /// </summary> 10 public partial class App : Application 11 { 12 protected override void OnStartup(StartupEventArgs e) 13 { 14 base.OnStartup(e); 15 16 this.ShutdownMode = ShutdownMode.OnExplicitShutdown; 17 18 var w = new MainWindow(); 19 20 // 弱参照でウォッチ 21 _r = new WeakReference<object>(w); 22 23 // ウィンドウを開いて閉じる 24 w.Show(); 25 w.Close(); 26 w = null; 27 this.MainWindow = null; 28 29 // GC を強制的におこなう 30 GC.Collect(); 31 GC.WaitForPendingFinalizers(); 32 GC.Collect(); 33 MessageBox.Show("GC.Collect() しました。"); 34 35 // 生存確認 36 object obj; 37 if (_r.TryGetTarget(out obj)) 38 { 39 MessageBox.Show(obj + " を参照中。"); 40 } 41 else 42 { 43 MessageBox.Show("参照していません。"); 44 } 45 46 // 終了 47 this.Shutdown(); 48 } 49 50 private WeakReference<object> _r; 51 } 52}

XAML

1<!-- MainWindow.xaml --> 2<!-- 初期状態から何も変更していません --> 3<Window x:Class="WpfApplication3.MainWindow" 4 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 5 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 6 Title="MainWindow" Height="350" Width="525"> 7 <Grid> 8 9 </Grid> 10</Window>

あっさり GC してくれると思っていましたが、
それほど単純ではないようです。

さらに上記のコードの内、

C#

1 //w.Show(); 2 //w.Close();

というように、表示すらしないようにしても GC されないようです。

一体誰がどこで MainWindow を参照し、GC を妨げているのか教えてください。

途中経過

調査している中で、App.Windows プロパティが怪しいかと思いましたが、そうではありませんでした。
このプロパティは「アプリケーションでインスタンス化されたウィンドウを取得」できるようです。
このコレクションは読み取り専用で、クリアしたり各要素に null を代入したりすることができないようです。
ただし、Window.Close() メソッドが呼ばれるとそのコレクションから参照が削除されます。参考ソース
よって、App.Windows プロパティに関しては Close() メソッドさえ呼べば問題ないということがわかりました。


App.Shutdown() を呼んだ後、OnExit() メソッドをオーバーライドして参照を確認したところ、
見事 GC によって破棄されていました。
ソースを確認すると、Dispatcher.BeginInvoke() によって OnExite() メソッドを非同期的に呼び出しているため、これによって後述する状況と同じことになり、たまたま Shutdown() で GC されるようになっただけと言えそうです。


2019/3/22 13:50 現在、皆様のおかげでここまできました。
現在下記のコードにて MainWindow が GC によって回収されることが確認できています。

C#

1namespace WpfApplication3 2{ 3 using System; 4 using System.Threading.Tasks; 5 using System.Windows; 6 7 /// <summary> 8 /// App.xaml の相互作用ロジック 9 /// </summary> 10 public partial class App : Application 11 { 12 protected override async void OnStartup(StartupEventArgs e) 13 { 14 base.OnStartup(e); 15 16 this.ShutdownMode = ShutdownMode.OnExplicitShutdown; 17 18 var w = new MainWindow(); 19 20 // 弱参照でウォッチ 21 var r = new WeakReference<object>(w); 22 23 // ウィンドウを開いて閉じる 24 w.Show(); 25 w.Close(); 26 w = null; 27 28 // 時間をおいてから GC を強制的におこなう 29 await Task.Delay(10); 30 GC.Collect(); 31 GC.WaitForPendingFinalizers(); 32 GC.Collect(); 33 34 // WM_DESTROY の確認 35 MessageBox.Show("IsDestroy = " + IsDestroy); 36 37 // 生存確認 38 object obj; 39 if (r.TryGetTarget(out obj)) 40 { 41 MessageBox.Show(obj + " を参照中。"); 42 } 43 else 44 { 45 MessageBox.Show("参照していません。"); 46 } 47 48 // 終了 49 this.Shutdown(); 50 } 51 52 public static bool IsDestroy { get; set; } 53 } 54}

MainWindow の Close() メソッドを呼んでから GC.Collect() を呼ぶまでの間に、非同期的に時間を置くことで GC されるようになるようです。
上記のコードでは 10[ms] しか待っていないため、10 回に 4 回ほどの割合でしか GC されません。Task.Delay() の時間を(私の環境では 100[ms] に)延ばすとほぼ確実に GC されるようになりました。

このことをもう少し詳しく調査するため、WM_DESTROY を捕捉してそのメッセージが投げられたかどうかを確認しましたが、WM_DESTROY は必ず投げられるようで、上記の "IsDestroy = " のメッセージボックスは常に true となりました。それでも MainWindow が GC されるときとされないときがあることを確認しました。
このときの MainWindow.xaml.cs のコードは次のようになります。

C#

1// MainWindow.xaml.cs 2namespace WpfApplication3 3{ 4 using System; 5 using System.Runtime.InteropServices; 6 using System.Windows; 7 using System.Windows.Interop; 8 9 /// <summary> 10 /// MainWindow.xaml の相互作用ロジック 11 /// </summary> 12 public partial class MainWindow : Window 13 { 14 public MainWindow() 15 { 16 InitializeComponent(); 17 18 this.SourceInitialized += OnSourceInitialized; 19 } 20 21 private void OnSourceInitialized(object sender, EventArgs e) 22 { 23 // メッセージ処理をフック 24 var handle = (new WindowInteropHelper(sender as Window)).Handle; 25 var hwndSource = HwndSource.FromHwnd(handle); 26 hwndSource.AddHook(WndProc); 27 } 28 29 private static IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) 30 { 31 if (msg == WM_DESTROY) 32 { 33 App.IsDestroy = true; 34 } 35 36 return IntPtr.Zero; 37 } 38 39 private const int WM_DESTROY = 0x0002; 40 } 41}

まとめ

これまでの調査によって、以下のことが明確になりました。

  1. Window クラスを new するとその参照が Application.Windows コレクションに追加される
  2. Window.Close() メソッドを呼ぶか Application.Shutdown() メソッドを呼ぶとApplication.Windows コレクションから参照が除外される

したがって、new した Window クラスへの参照は、ただ null を代入しても GC されずに残るため、必ず Close() メソッドを呼ぶ必要がある。ただし、「他からの参照」が生き残っているため、Close() メソッドを呼んだだけでは GC の対象にはならない。ある程度時間を(今回の私の環境では 100[ms])置くと「他からの参照」がなくなり、GC の対象となる。

この質問スレッドもだいぶ文章が長くなってきましたので、「他からの参照」というのが何者なのかについてはここでは追究しません。
今後の調査によってはまた質問を投稿させていただきますので、そのときはまた皆様にご協力いただきたいと思います。中々に濃い内容だったように思いますが、これまでお付き合いいただきありがとうございました。

TakuyaK77, hihijiji, yamame, kikukiku, atata0319👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

kikukiku

2019/03/20 05:57

自身のアプリから、自身のアプリのGCを実行しても、起動中なので回収されないのでは?
twyujiro15

2019/03/20 06:10

上記のコードのように、変数 w はあくまでも内部変数なので、このメソッド内の不要になるところから GC の対象になると思っています。が、そうはなっていないようなので投稿した次第です。
S_kawa

2019/03/20 06:14

GC強制のお作法的にはこんな感じに3行書くのが習慣ですね。 GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); //ファイナライズされたものを対象に ※今回のケースに当てはまるかは精査してません 表示云々というよりインスタンス化の時点でイベントハンドラがどうなるかなという気がします。
kikukiku

2019/03/20 06:17

なるほど。知識不足でした。 だとしたら、参照しなくなるまで、待ち続ける必要があるかもです。
twyujiro15

2019/03/20 06:59

S_kawa さんの言うように、確かにお作法的には 3 行が必要かと思いますので質問文のほうのコードも修正させていただきました。イベントハンドラに関して懸念があるとのことですが、具体的に誰が持っているイベント、もしくはハンドラのことなのでしょうか。 作業している中で、そういえば Window クラスのコンストラクタで InitializeComponent() メソッドが呼ばれていることにふと気が付き、少しコードを追いかけて見ていますが、何か参照を保持するようなコードは今のところ見当たりません。というか正直よくわかりません。 kikukiku さん、参考 URL ありがとうございます。こちらのサンプルコードは過去に WeakReference<T> について調べていたときに試したことがありますが、なぜか私の環境では永遠に破棄されないようです。代わりに下記のコードは私の環境でも記事通りに動作しました。 http://pro.art55.jp/?eid=1109086
kikukiku

2019/03/20 07:18

実験の結果をフィードバック頂きありがとうございます。 勉強になります。 ちょっと自身の知識ではお手上げかも。すみません。
wwbQzhMkhhgEmhU

2019/03/20 20:28 編集

多分Window.Close()で発生したClosing/Closedイベントがまだ処理されておらず、普通にWindowが閉じられていないだけだと思います。asyncにしてあげた上で(初期化処理のasyncって気持ち悪いけど)Close直後に await Task.Delay(1000); とか入れてあげれば、UIスレッドがMainWindowのClosing/Closedイベントを処理してくれて破棄されて、弱参照が外れるはずです。 と思ってたのですが、気になってみて書いてみたらClosing/Closedともに呼ばれてました。勘違いでしたすみません。
wwbQzhMkhhgEmhU

2019/03/22 05:10 編集

質問の回答ではなく、回答にも無関係な質問なのですが、質問内容が不明なので一応聞いておきますね。 デフォルトのエントリポイントはMainWindowを作成すると思うのですが、それをさらにOnStartUpで作成するという形はいいのでしょうか? それとも何かMainWindowを作成しない仕組みが組み込まれているのでしょうか? 私は上の検証をしたとき、エントリポイントを作成してApplicationの外で検証した(MainWindowは普通に×ボタンで閉じます)のですが、Application.Runが終わってからgcをかけても回収はされないようでした。 public class EntryPoint { private static WeakReference<object> _r; [STAThread] public static void Main() { Application app = new App(); var w = new MainWindow(); // 弱参照でウォッチ _r = new WeakReference<object>(w); app.Run(w); w = null; // GC を強制的におこなう GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); MessageBox.Show("GC.Collect() しました。"); // 生存確認 object obj; if (_r.TryGetTarget(out obj)) { MessageBox.Show(obj + " を参照中。"); } else { MessageBox.Show("参照していません。"); } } } ※プロジェクトの設定でエントリポイントを変更しないと重複エラーになります
wwbQzhMkhhgEmhU

2019/03/22 05:24

えーっと、MainWindowのコンストラクタ2回呼ばれてませんか?
twyujiro15

2019/03/22 06:01 編集

検証ありがとうございます。 始めに App.xaml にある StartupUri="MainWindow" の記述を削除していますので、App.Run() が実行されても自動的に MainWindow は作成されないはずです。 また、上記のコードで GC されない理由は、Window クラスの Close() メソッドを呼んでいないため、Application.Windows プロパティのコレクションから参照されていることが原因かと思われます。このことから、WPF では new した Window は必ず Show() → Close() しないとメモリリークすると言えると思います。また、Close() するためには必ず Show() しないと例外が発生するため、作法としては一度も Show() する気のない Window は new してはいけないというような感じでしょうか。 何度か質問文を編集している中で、質問内容が不明確となってしまったタイミングがあり申し訳ありませんでした。 --- 「メモリリーク」という表現は不適切でした。開発者側のコードで明示的に new しているのならそれは「リーク」ではなく「キャッシュ」など別の言い回しになりますね。ただ、その「キャッシュ」を破棄する場合、単純に null で開発者側からの参照を抹消するだけでなく、必ず Show() → Close() という手順を踏まないといけないという意味では「メモリリーク予備軍」であると言えると思います。
wwbQzhMkhhgEmhU

2019/03/22 05:32

あ、xamlに書いてあったんですね。気付きませんでした。 Close()ではなく、×ボタンで閉じてるので、リークはしませんよ。 closedイベントでdisposeされています。 また、Delayで待てばgcされるのも同じです。
twyujiro15

2019/03/22 05:39

Closed イベントで何が Dispose されているのでしょうか。また、なぜ時間を置くことで GC されるのでしょうか。具体的に教えていただけないでしょうか。
wwbQzhMkhhgEmhU

2019/03/22 05:49

> Closed イベントで何が Dispose されているのでしょうか。 MainWindowのインスタンスです。 > なぜ時間を置くことで GC されるのでしょうか これは回答ではないと、書いてあるとおりで、正確なところは分かりません。 挙動から見れば、ディスパッチャがまだ処理できてないイベントがあって、それが実行できたときに参照が消えるのではないかと思っています。コードを追うのは面倒なので、追ってませんが、そこには食いつかないでくださいw
twyujiro15

2019/03/22 06:04

>> Closed イベントで何が Dispose されているのでしょうか。 > MainWindowのインスタンスです。 つまり Application クラスから MainWindow のインスタンスが完全に破棄されている、ということでしょうか。 > ディスパッチャがまだ処理できてないイベントがあって そこのところをもっと詳しく知りたいのですが、食いついてはいけませんかw これから私がその部分を調査するに当たって、その入り口というか、ヒントのようなものだけでも教えていただけませんか。
wwbQzhMkhhgEmhU

2019/03/22 06:19

> つまり Application クラスから MainWindow のインスタンスが完全に破棄されている、ということでしょうか。 完全破棄ではないです。IDisposableを実装したDispose()が最低1回呼ばれている状態というだけですね。 gcのタイミングで、finalize可能な状態ということです。 > そこのところをもっと詳しく知りたいのですが、食いついてはいけませんかw > これから私がその部分を調査するに当たって、その入り口というか、ヒントのようなものだけでも教えていただけませんか。 外からのイベントがやってきたのを処理するのはディスパッチャの役目ですが、処理したことになってても、BeginInvokeなどで後回しになってるやつがあるんじゃないか?と思ってるだけです。ただの勘です。
twyujiro15

2019/03/22 06:37

> 完全破棄ではないです。IDisposableを実装したDispose()が最低1回呼ばれている状態というだけですね。 「完全」はちょっと言い過ぎました。失礼しました。 > gcのタイミングで、finalize可能な状態ということです。 質問文のコードでは GC.WaitForPendingFinalizers() を使って行儀よくしていますが、GC.Collect() の 1 行だけでも MainWindow のインスタンスが破棄されることを確認していますので、今回の場合はファイナライザを考慮しなくても良いと思っています。 > BeginInvokeなどで後回しになってるやつがあるんじゃないか? ありがとうございます。例えただの勘であっても調査の糸口をいただけて非常に助かります。
wwbQzhMkhhgEmhU

2019/03/22 14:10

まぁ少し残念な結果になりましたが、コードを血眼で探さなくても分かることだけ調べてみました。 まず、WM_DESTROYを追いかけるコードは意味をなしていません。 Window.Close()はWM_CLOSEメッセージを発行しており、処理上のどこかでWM_DESTROYメッセージ相当も発行されています。 WM_CLOSEはclosingイベントで処理され、WM_DESTROYはclosedイベントで処理されます。 これらは、普通にイベントハンドラを書けば呼ばれるので、そのときのコールスタックを見ることで、Window.Close()由来であることは確認できるはずです。 同時に、WM_DESTROYメッセージのイベントはソースコード上、Window. WmDestroy()で処理されているのも分かるので、ここを見ると、InternalDispose()が呼ばれているので、dispose相当のことがなされていることが分かります(よく見たらIDisposableは実装していませんね)。 次に、そこまで実行しておいて、なぜgcの対象にならないのか?そして時間が経つとなぜgcの対象になるのか、これは明確には分かっていませんが、Dispatcherの残り処理ではないか?と言った根拠だけ書いておきます。 VSのウォッチウィンドウで、 Application.Current.Dispatcher._queue._count を見ていたからです。これをClose直前と直後、及びDelayの直前と直後で見てみてください。 Closeの前後で、増えており、Delayの前後で0になりました。 つまりClose直後にはキューに残っていて未処理のものがあり、それが全部処理されてからgcすると消えているということです。何が積まれているのかは分かりませんが、その中にMainWindowのインスタンスが含まれていたとして、何も不思議はないと思います。なお、AppのディスパッチャもMainWindowのディスパッチャも同じものです。 以上、まだまだ明確ではありませんが、目的も不明な段階でこれ以上の調査は意味がないと考え、私はそこで考えるのを止めました。解決済みになってたので、未提出の中途半端な情報も記述しておいた次第です。 お疲れ様でした。
twyujiro15

2019/03/23 06:21

詳細な補足ありがとうございます。確かにキューが増減することも確認できました。 ここに質問を投稿する前は「WPF ではウィンドウを閉じてもメモリから破棄されないの?」と不安でしたが、「Show()→Close() すればそのうち破棄される」と自信を持って人に説明できるようになりましたので、ここでの議論はお開きとさせていただきました。 今後調査に行き詰ったら別途質問を投稿しますので、見かけたらまたよろしくお願いします。 ありがとうございました。
guest

回答4

0

以下のコードで検証してみた結果、Window はガベージコレクションされないようです。
おそらく内部で自己参照しているのでしょう。

C#

1using System; 2using System.Diagnostics; 3using System.Threading.Tasks; 4using System.Windows; 5using System.Windows.Controls; 6 7namespace WpfApp1 8{ 9 public partial class App : Application 10 { 11 private async void Application_Startup(object sender, StartupEventArgs e) 12 { 13 var references = new[] 14 { 15 (typeof(MainWindow).Name, new WeakReference(new MainWindow())), 16 (typeof(Window).Name, new WeakReference(new Window())), 17 (typeof(ContentControl).Name, new WeakReference(new ContentControl())), 18 (typeof(Control).Name, new WeakReference(new Control())), 19 (typeof(object).Name, new WeakReference(new object())) 20 }; 21 await Task.Run(async () => 22 { 23 while (true) 24 { 25 Debug.WriteLine("-------------"); 26 foreach (var (Name, Reference) in references) 27 { 28 if (Reference.IsAlive) 29 { 30 Debug.WriteLine($"{Name} is alive."); 31 } 32 else 33 { 34 Debug.WriteLine($"{Name} is dead."); 35 } 36 } 37 await Task.Delay(1000); 38 GC.Collect(); 39 } 40 }); 41 } 42 } 43}

投稿2019/03/20 07:34

Zuishin

総合スコア28660

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

hihijiji

2019/03/20 07:49

素晴らしいです。
hihijiji

2019/03/21 01:08

Window はClose()でリソースを手放すみたいですね。 ちょっと書き足しました。 new Window(); // Application の終了を防ぐためのインスタンス await Task.Run(async () => { while (true) { Debug.WriteLine("-------------"); foreach (var (Name, Reference) in references) { if (Reference.IsAlive) { Debug.WriteLine($"{Name} is alive."); if(Reference.Target is Window window) { window?.Dispatcher?.Invoke(() => window?.Close()); } } else { Debug.WriteLine($"{Name} is dead."); } } await Task.Delay(1000); GC.Collect(); } });
Zuishin

2019/03/21 01:13

どうなったのか気になるのでぜひ自分の回答でお願いします。私の回答は結局「リソースがどこで参照されてるかわかりませーん」と書いただけですから。
guest

0

mituhaさんの回答を参考にちょっと調べてみました。

Windowが作成される際に、Application.Windowsに管理登録されてしまっているようです。
そこからの参照を切らない限り、GC回収されないようです。

参照を切る方法としてはCloseを呼べばいいのですが、一度Showがされていると、ウィンドウリソースの管理もあるので、そのリソースが破棄されるWM_DESTROYのメッセージが処理されるまで、参照が解除されませんでした。

今回のケースだと、Shutdown内でその処理が行われるようなので、その後にGCをすると破棄されていました。

c#

1 // ウィンドウを開いて閉じる 2 w.Show(); 3 this.MainWindow = null; 4 w.Close(); 5 w = null; 6 7 // GC を強制的におこなう 8 GC.Collect(); 9 GC.WaitForPendingFinalizers(); 10 GC.Collect(); 11 12 // 生存確認 13 { 14 object obj; 15 if (_r.TryGetTarget(out obj)) { 16 MessageBox.Show(obj + " を参照中。"); 17 obj = null; 18 } 19 else { 20 MessageBox.Show("参照していません。"); 21 } 22 } 23 24 // 終了 25 this.Shutdown(); 26 27 GC.Collect(); 28 GC.WaitForPendingFinalizers(); 29 GC.Collect(); 30 // 生存確認 31 { 32 object obj; 33 if (_r.TryGetTarget(out obj)) { 34 MessageBox.Show(obj + " を参照中。"); 35 obj = null; 36 } 37 else { 38 MessageBox.Show("参照していません。"); 39 } 40 } 41

投稿2019/03/21 01:49

ta.fu

総合スコア1667

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

twyujiro15

2019/03/21 23:52

確かに、Shutdown() してから参照を確認してみると、見事に破棄されていました!Shutdown() 以降に処理を書くという発想がなかったので目からウロコでした…。 Shutdown() を呼ぶと内部から OnExit() がコールバックとして呼ばれるため、こちらをオーバーライドして同じく参照を確認したところ、この時点で既に参照が破棄されているようでした。 Microsoft Reference Source でソースを確認しているのですが、Shutdown() メソッドが呼ばれてから OnExit() メソッドがコールバックされるまでの間にリソース管理に関するコードが見当たらず、イマイチ釈然としません。ただ、OnExit() メソッドは Dispatcher.BeginInvoke() を挟んでコールされているので、その間に目の前のコード以外のところで何かが働いているのでしょうか。
ta.fu

2019/03/22 00:39

前回の書き込みでは、WM_DESTROYのメッセージ処理に当たりをつけてみましたが、改めてApplication.Shutdown(実際にはDoShutdown)を見てみたら、内部でWindowsInternalを空っぽにしてました。たぶん、ここで解放されているのかも。 ただ、ソースコードを見てのピンポイント解釈なので、ほかにも影響を及ぼしているところがあるのではないかと思います。
twyujiro15

2019/03/22 03:07

Window.Close() メソッドが呼ばれることによって、そのウィンドウの参照は WindowsInternal から除外されるようなので、質問文にあるコードでは、Shutdown() を呼ぶ時点で既に Applicaton.Windows プロパティは空っぽの状態です。 やはりおっしゃるように、他にも影響を及ぼしているところがあるようで、そこをなんとか明確にしたいと思っています。
guest

0

Application.MainWindow あたりが保持していそうです。参考ソース
ちゃんと見てないので正しいかはわかりませんが、Window作成時に参照はされていそうなので、この辺の後処理もしないと回収されないかもしれません。

きちんと調べたいのであれば、WinDbg等で参照を追いかけてみてはいかがでしょうか?

投稿2019/03/20 06:12

編集2019/03/20 06:25
mituha

総合スコア385

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

twyujiro15

2019/03/20 07:06

すみません。一度そのプロパティを確認し、確かに MainWindow の参照を保持していたようなので null を入れて再度確認していました。ただ、そうしてもなおこの現象から抜けだせなかったので投稿しております。 質問文のコードからこの null 代入文が抜けていましたので追加させていただきます。ご指摘ありがとうございます。
twyujiro15

2019/03/20 07:21

参考ソースありがとうございます。MainWindow クラスのコンストラクタしか見えていませんでしたが、確かに Window クラスのコンストラクタを見るべきでした。Application.MainWindow プロパティも参照を保持していますが、その直前の App.WindowsInternal.Add(this); つまり、Application.Windows プロパティのほうが強烈なようです。
guest

0

ベストアンサー

GC.Collect() はガベージコレクタの実行を強要するだけで、同期的にガベージコレクタの完了を待ったりはしません。
(Disposeパターンが実装されていて)Disposeを呼ばれたあとはファイナライザが走らないので、回収されたかも確実に知る方法は多分ないと思います。
適度にウェイトを入れてから確認するぐらいでしょう。

--- 追記 ---
Zuishinさんのコードを借りて確認したところ、Close()すれば回収はされるようです。
Windowはファイナライザが走らないと仮説を立てましたが、検証コードは能力不足で書けませんでした。

C#

1private async void Application_Startup(object sender, StartupEventArgs e) 2{ 3 var references = new[] 4 { 5 (typeof(MainWindow).Name, new WeakReference(new MainWindow())), 6 (typeof(Window).Name, new WeakReference(new Window())), 7 (typeof(ContentControl).Name, new WeakReference(new ContentControl())), 8 (typeof(Control).Name, new WeakReference(new Control())), 9 (typeof(object).Name, new WeakReference(new object())) 10 }; 11 12 new Window(); // Application の終了を防ぐためのインスタンス 13 14 await Task.Run(async () => 15 { 16 while (true) 17 { 18 Debug.WriteLine("-------------"); 19 foreach (var (Name, Reference) in references) 20 { 21 if (Reference.IsAlive) 22 { 23 Debug.WriteLine($"{Name} is alive."); 24 if (Reference.Target is Window window) 25 { 26 window?.Dispatcher?.Invoke(() => window?.Show()); 27 await Task.Delay(2000); 28 window?.Dispatcher?.Invoke(() => window?.Close()); 29 } 30 } 31 else 32 { 33 Debug.WriteLine($"{Name} is dead."); 34 } 35 } 36 await Task.Delay(1000); 37 GC.Collect(); 38 } 39 }); 40}

投稿2019/03/20 05:26

編集2019/03/21 09:25
hihijiji

総合スコア4150

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

twyujiro15

2019/03/20 06:03 編集

ご指摘ありがとうございます。GC.WaitForPendingFinalizers() でファイナライザの終了を待機できると思いますので、質問文のコードにも追記しました。 ただ、結果は変わらず、GC で回収されないようです。 --追記-- すみません。「ファイナライザ」という言葉に敏感に反応し過ぎて誤解していたようです。別に「ファイナライザを待て」とは言っていませんでしたね。 ところで今動作確認しているのは System.Windows.Window クラスですが、このクラスは IDisposable を実装しているわけではありません。その上で上記のコメントが意味するところはどういうことなのか教えていただけないでしょうか。今の私には勉強不足で理解できませんでした。
hihijiji

2019/03/20 06:45

Window が Disposable を実装していると思い込んでいました。m(_ _)m
hihijiji

2019/03/20 07:07

そもそも 変数wの有効なスコープ内では w = null; しても参照切れ扱いしてくれないのでは?
twyujiro15

2019/03/22 00:29 編集

継続的な検証ありがとうございます。 追記されたコードですが、 this.ShutdownMode = ShutdownMode.OnExplicitShutdown; としていないのではないでしょうか。この場合、App.MainWindow プロパティに指定されるウィンドウの Close() メソッドが呼ばれたときに Application.Shutdown() と同じコードが走るようになっているようです。 (ソース)[ https://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Window.cs,4639 ] 結局 Application.Shutdown() メソッドがキーになっているように思います。
hihijiji

2019/03/22 02:06

ShutdownMode は規定値 OnLastWindowClose のままです。 コード中にもあるように、Application の終了を防ぐためのインスタンスを作っているので Shutdown は実行されません。
twyujiro15

2019/03/22 03:42

すみません。既定値を OnMainWindowClose と勘違いしていました。確かに既定値は OnLastWindowClose で、new Window() によって Shutdown() は呼ばれないようになっていますね。 そうすると、Application.Shutdown() を必要としないという意味では、ta.fu さんの回答とは違う状況かと思います。 しかし、私の環境ではwhile ループを 10 回以上回っても MainWindow が回収されません。ただし、掲載いただいたコードに null 条件演算子やタプルなどがあり、C# のバージョンが古い VS2013 ではそのまま実行できないため、修正しながら動作確認をしています。また、while ループ内で MainWindow に対して 2 回以上 Show() してしまうようなコードになっていると思うので、こちらは回避するようにしながら確認をしています。
twyujiro15

2019/03/22 04:44

動作を確認できました!どうやら Close() メソッドを呼んだ後、時間をおいてから GC しているところがミソのようです。質問文のほうにも追記します。
hihijiji

2019/03/22 07:18

ベストアンサーは、Zuishinさんがふさわしいと思います。 私は追試しただけです。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問