teratail header banner
teratail header banner
質問するログイン新規登録

質問編集履歴

16

誤記修正。

2019/03/22 08:11

投稿

twyujiro15
twyujiro15

スコア217

title CHANGED
File without changes
body CHANGED
@@ -227,7 +227,7 @@
227
227
  これまでの調査によって、以下のことが明確になりました。
228
228
 
229
229
  1. Window クラスを new するとその参照が Application.Windows コレクションに追加される
230
- 1. Window.Close() メソッドを呼ぶか Application.Shutdown() メソッドを呼ぶとApplication.Windows コレクションから参照除外される
230
+ 1. Window.Close() メソッドを呼ぶか Application.Shutdown() メソッドを呼ぶとApplication.Windows コレクションから参照除外される
231
231
 
232
232
  したがって、new した Window クラスへの参照は、ただ null を代入しても GC されずに残るため、必ず Close() メソッドを呼ぶ必要がある。ただし、「他からの参照」が生き残っているため、Close() メソッドを呼んだだけでは GC の対象にはならない。ある程度時間を(今回の私の環境では 100[ms])置くと「他からの参照」がなくなり、GC の対象となる。
233
233
 

15

まとめを追記。

2019/03/22 08:11

投稿

twyujiro15
twyujiro15

スコア217

title CHANGED
File without changes
body CHANGED
@@ -222,4 +222,14 @@
222
222
  private const int WM_DESTROY = 0x0002;
223
223
  }
224
224
  }
225
- ```
225
+ ```
226
+ ### まとめ
227
+ これまでの調査によって、以下のことが明確になりました。
228
+
229
+ 1. Window クラスを new するとその参照が Application.Windows コレクションに追加される
230
+ 1. Window.Close() メソッドを呼ぶか Application.Shutdown() メソッドを呼ぶとApplication.Windows コレクションから参照を除外される
231
+
232
+ したがって、new した Window クラスへの参照は、ただ null を代入しても GC されずに残るため、必ず Close() メソッドを呼ぶ必要がある。ただし、「他からの参照」が生き残っているため、Close() メソッドを呼んだだけでは GC の対象にはならない。ある程度時間を(今回の私の環境では 100[ms])置くと「他からの参照」がなくなり、GC の対象となる。
233
+
234
+ この質問スレッドもだいぶ文章が長くなってきましたので、「他からの参照」というのが何者なのかについてはここでは追究しません。
235
+ 今後の調査によってはまた質問を投稿させていただきますので、そのときはまた皆様にご協力いただきたいと思います。中々に濃い内容だったように思いますが、これまでお付き合いいただきありがとうございました。

14

文章を整理。

2019/03/22 06:51

投稿

twyujiro15
twyujiro15

スコア217

title CHANGED
File without changes
body CHANGED
@@ -1,5 +1,7 @@
1
+ ### 開発環境
1
2
  Visual Studio 2013、.NET Framework 4.6 で WPF アプリの開発をしています。
2
3
 
4
+ ### 質問内容
3
5
  下記のようなコードを Release ビルドし、
4
6
  exe から単体で起動したところ、
5
7
  ウィンドウを閉じた後に GC をしているにも関わらず、
@@ -100,7 +102,7 @@
100
102
 
101
103
  一体誰がどこで MainWindow を参照し、GC を妨げているのか教えてください。
102
104
 
103
- ---
105
+ ### 途中経過
104
106
  調査している中で、App.Windows プロパティが怪しいかと思いましたが、そうではありませんでした。
105
107
  このプロパティは「アプリケーションでインスタンス化されたウィンドウを取得」できるようです。
106
108
  このコレクションは読み取り専用で、クリアしたり各要素に null を代入したりすることができないようです。
@@ -220,6 +222,4 @@
220
222
  private const int WM_DESTROY = 0x0002;
221
223
  }
222
224
  }
223
- ```
225
+ ```
224
-
225
- Close() メソッドを呼んだ後、なぜ少し時間を置く必要があるのかを教えてください。

13

WM_CLOSE を WM_DESTROY に変更。

2019/03/22 06:39

投稿

twyujiro15
twyujiro15

スコア217

title CHANGED
File without changes
body CHANGED
@@ -6,7 +6,7 @@
6
6
  ウィンドウの生存が確認されました。
7
7
 
8
8
  最新の動作確認用コードは一番下にあります。
9
- WM_CLOSE を処理しているにも関わらず GC で回収されないことがあります。
9
+ WM_DESTROY を処理しているにも関わらず GC で回収されないことがあります。
10
10
 
11
11
  ```XAML
12
12
  <!-- App.xaml -->
@@ -150,8 +150,8 @@
150
150
  GC.WaitForPendingFinalizers();
151
151
  GC.Collect();
152
152
 
153
- // WM_CLOSE の確認
153
+ // WM_DESTROY の確認
154
- MessageBox.Show("IsClose = " + IsClose);
154
+ MessageBox.Show("IsDestroy = " + IsDestroy);
155
155
 
156
156
  // 生存確認
157
157
  object obj;
@@ -168,7 +168,7 @@
168
168
  this.Shutdown();
169
169
  }
170
170
 
171
- public static bool IsClose { get; set; }
171
+ public static bool IsDestroy { get; set; }
172
172
  }
173
173
  }
174
174
  ```
@@ -176,7 +176,7 @@
176
176
  MainWindow の Close() メソッドを呼んでから GC.Collect() を呼ぶまでの間に、非同期的に時間を置くことで GC されるようになるようです。
177
177
  上記のコードでは 10[ms] しか待っていないため、10 回に 4 回ほどの割合でしか GC されません。Task.Delay() の時間を(私の環境では 100[ms] に)延ばすとほぼ確実に GC されるようになりました。
178
178
 
179
- このことをもう少し詳しく調査するため、WM_CLOSE を捕捉してそのメッセージが投げられたかどうかを確認しましたが、WM_CLOSE は必ず投げられるようで、上記の "IsClose = " のメッセージボックスは常に true となりました。それでも MainWindow が GC されるときとされないときがあることを確認しました。
179
+ このことをもう少し詳しく調査するため、WM_DESTROY を捕捉してそのメッセージが投げられたかどうかを確認しましたが、WM_DESTROY は必ず投げられるようで、上記の "IsDestroy = " のメッセージボックスは常に true となりました。それでも MainWindow が GC されるときとされないときがあることを確認しました。
180
180
  このときの MainWindow.xaml.cs のコードは次のようになります。
181
181
  ```C#
182
182
  // MainWindow.xaml.cs
@@ -209,15 +209,15 @@
209
209
 
210
210
  private static IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
211
211
  {
212
- if (msg == WM_CLOSE)
212
+ if (msg == WM_DESTROY)
213
213
  {
214
- App.IsClose = true;
214
+ App.IsDestroy = true;
215
215
  }
216
216
 
217
217
  return IntPtr.Zero;
218
218
  }
219
219
 
220
- private const int WM_CLOSE = 0x0010;
220
+ private const int WM_DESTROY = 0x0002;
221
221
  }
222
222
  }
223
223
  ```

12

誤記修正。

2019/03/22 05:36

投稿

twyujiro15
twyujiro15

スコア217

title CHANGED
File without changes
body CHANGED
@@ -174,7 +174,7 @@
174
174
  ```
175
175
 
176
176
  MainWindow の Close() メソッドを呼んでから GC.Collect() を呼ぶまでの間に、非同期的に時間を置くことで GC されるようになるようです。
177
- 上記のコードでは 10[ms] しか待っていないため、10 回に 4 回ほどの割合で GC されません。Task.Delay() の時間を(私の環境では 100[ms] に)延ばすとほぼ確実に GC されるようになりました。
177
+ 上記のコードでは 10[ms] しか待っていないため、10 回に 4 回ほどの割合でしか GC されません。Task.Delay() の時間を(私の環境では 100[ms] に)延ばすとほぼ確実に GC されるようになりました。
178
178
 
179
179
  このことをもう少し詳しく調査するため、WM_CLOSE を捕捉してそのメッセージが投げられたかどうかを確認しましたが、WM_CLOSE は必ず投げられるようで、上記の "IsClose = " のメッセージボックスは常に true となりました。それでも MainWindow が GC されるときとされないときがあることを確認しました。
180
180
  このときの MainWindow.xaml.cs のコードは次のようになります。

11

誤記修正。

2019/03/22 05:30

投稿

twyujiro15
twyujiro15

スコア217

title CHANGED
File without changes
body CHANGED
@@ -6,7 +6,7 @@
6
6
  ウィンドウの生存が確認されました。
7
7
 
8
8
  最新の動作確認用コードは一番下にあります。
9
- WM_CLOSE を処理する/いに関係なく GC で回収されないことがあります。
9
+ WM_CLOSE を処理しわらず GC で回収されないことがあります。
10
10
 
11
11
  ```XAML
12
12
  <!-- App.xaml -->

10

WM_CLOSE に関する補足を追記。

2019/03/22 05:20

投稿

twyujiro15
twyujiro15

スコア217

title CHANGED
File without changes
body CHANGED
@@ -5,6 +5,9 @@
5
5
  ウィンドウを閉じた後に GC をしているにも関わらず、
6
6
  ウィンドウの生存が確認されました。
7
7
 
8
+ 最新の動作確認用コードは一番下にあります。
9
+ WM_CLOSE を処理する/しないに関係なく GC で回収されないことがあります。
10
+
8
11
  ```XAML
9
12
  <!-- App.xaml -->
10
13
  <!-- StartupUri プロパティの指定を削除しただけです -->
@@ -147,6 +150,9 @@
147
150
  GC.WaitForPendingFinalizers();
148
151
  GC.Collect();
149
152
 
153
+ // WM_CLOSE の確認
154
+ MessageBox.Show("IsClose = " + IsClose);
155
+
150
156
  // 生存確認
151
157
  object obj;
152
158
  if (r.TryGetTarget(out obj))
@@ -161,9 +167,59 @@
161
167
  // 終了
162
168
  this.Shutdown();
163
169
  }
170
+
171
+ public static bool IsClose { get; set; }
164
172
  }
165
173
  }
166
174
  ```
167
175
 
168
176
  MainWindow の Close() メソッドを呼んでから GC.Collect() を呼ぶまでの間に、非同期的に時間を置くことで GC されるようになるようです。
169
- 上記のコードでは 10[ms] しか待っていないため、10 回に 4 回ほどの割合で GC されま。Task.Delay() の時間を(私の環境では 100[ms] に)延ばすとほぼ確実に GC されるようになりました。
177
+ 上記のコードでは 10[ms] しか待っていないため、10 回に 4 回ほどの割合で GC されません。Task.Delay() の時間を(私の環境では 100[ms] に)延ばすとほぼ確実に GC されるようになりました。
178
+
179
+ このことをもう少し詳しく調査するため、WM_CLOSE を捕捉してそのメッセージが投げられたかどうかを確認しましたが、WM_CLOSE は必ず投げられるようで、上記の "IsClose = " のメッセージボックスは常に true となりました。それでも MainWindow が GC されるときとされないときがあることを確認しました。
180
+ このときの MainWindow.xaml.cs のコードは次のようになります。
181
+ ```C#
182
+ // MainWindow.xaml.cs
183
+ namespace WpfApplication3
184
+ {
185
+ using System;
186
+ using System.Runtime.InteropServices;
187
+ using System.Windows;
188
+ using System.Windows.Interop;
189
+
190
+ /// <summary>
191
+ /// MainWindow.xaml の相互作用ロジック
192
+ /// </summary>
193
+ public partial class MainWindow : Window
194
+ {
195
+ public MainWindow()
196
+ {
197
+ InitializeComponent();
198
+
199
+ this.SourceInitialized += OnSourceInitialized;
200
+ }
201
+
202
+ private void OnSourceInitialized(object sender, EventArgs e)
203
+ {
204
+ // メッセージ処理をフック
205
+ var handle = (new WindowInteropHelper(sender as Window)).Handle;
206
+ var hwndSource = HwndSource.FromHwnd(handle);
207
+ hwndSource.AddHook(WndProc);
208
+ }
209
+
210
+ private static IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
211
+ {
212
+ if (msg == WM_CLOSE)
213
+ {
214
+ App.IsClose = true;
215
+ }
216
+
217
+ return IntPtr.Zero;
218
+ }
219
+
220
+ private const int WM_CLOSE = 0x0010;
221
+ }
222
+ }
223
+ ```
224
+
225
+ Close() メソッドを呼んだ後、なぜ少し時間を置く必要があるのかを教えてください。

9

Close() 後に時間を置くと回収されることに関する補足を追記。

2019/03/22 05:19

投稿

twyujiro15
twyujiro15

スコア217

title CHANGED
File without changes
body CHANGED
@@ -107,15 +107,17 @@
107
107
  ---
108
108
  App.Shutdown() を呼んだ後、OnExit() メソッドをオーバーライドして参照を確認したところ、
109
109
  見事 GC によって破棄されていました。
110
- ただ、[ソース](https://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Application.cs,323f71efbcb142b9)を確認もリースなどの参照除外するようなコードが見当りません
111
- Shutdown() が呼ばれてから OnExit() メソッドが呼ばれるまでの間に何が起きているのか、
112
- そのコードはどこにあるのかを教えていただけないでしょうか。
110
+ [ソース](https://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Application.cs,323f71efbcb142b9)を確認すると、Dispatcher.BeginInvoke() によっ OnExite() メッド非同期的に呼び出しているため、これによって後述する状況と同じことになり、たまたま Shutdown() で GC されるようだけと言えそうです
113
111
 
112
+ ---
113
+ 2019/3/22 13:50 現在、皆様のおかげでここまできました。
114
- 現在動作確認しているコードは次の通りです。
114
+ 現在下記のコードにて MainWindow が GC によって回収されることが確認きています。
115
+
115
116
  ```C#
116
117
  namespace WpfApplication3
117
118
  {
118
119
  using System;
120
+ using System.Threading.Tasks;
119
121
  using System.Windows;
120
122
 
121
123
  /// <summary>
@@ -123,57 +125,45 @@
123
125
  /// </summary>
124
126
  public partial class App : Application
125
127
  {
126
- protected override void OnStartup(StartupEventArgs e)
128
+ protected override async void OnStartup(StartupEventArgs e)
127
129
  {
128
130
  base.OnStartup(e);
129
131
 
130
132
  this.ShutdownMode = ShutdownMode.OnExplicitShutdown;
131
133
 
132
134
  var w = new MainWindow();
135
+
133
136
  // 弱参照でウォッチ
134
- _r = new WeakReference<object>(w);
137
+ var r = new WeakReference<object>(w);
135
138
 
139
+ // ウィンドウを開いて閉じる
136
140
  w.Show();
137
141
  w.Close();
138
142
  w = null;
139
143
 
140
- // 生存確認
141
- CheckAlive();
142
-
143
- // 終了
144
- this.Shutdown();
145
- }
146
-
147
- protected override void OnExit(ExitEventArgs e)
148
- {
149
- //base.OnExit(e);
150
-
151
- // 生存確認
152
- CheckAlive();
153
- }
154
-
155
- private void CheckAlive()
156
- {
157
- // GC を強制的におこなう
144
+ // 時間をおいてから GC を強制的におこなう
145
+ await Task.Delay(10);
158
146
  GC.Collect();
159
147
  GC.WaitForPendingFinalizers();
160
148
  GC.Collect();
161
- MessageBox.Show("GC.Collect() しました。");
162
149
 
163
150
  // 生存確認
164
151
  object obj;
165
- if (_r.TryGetTarget(out obj))
152
+ if (r.TryGetTarget(out obj))
166
153
  {
167
154
  MessageBox.Show(obj + " を参照中。");
168
- obj = null;
169
155
  }
170
156
  else
171
157
  {
172
158
  MessageBox.Show("参照していません。");
173
159
  }
160
+
161
+ // 終了
162
+ this.Shutdown();
174
163
  }
175
-
176
- private WeakReference<object> _r;
177
164
  }
178
165
  }
179
- ```
166
+ ```
167
+
168
+ MainWindow の Close() メソッドを呼んでから GC.Collect() を呼ぶまでの間に、非同期的に時間を置くことで GC されるようになるようです。
169
+ 上記のコードでは 10[ms] しか待っていないため、10 回に 4 回ほどの割合で GC されます。Task.Delay() の時間を(私の環境では 100[ms] に)延ばすとほぼ確実に GC されるようになりました。

8

最新の動作確認用コードを追記。

2019/03/22 04:56

投稿

twyujiro15
twyujiro15

スコア217

title CHANGED
File without changes
body CHANGED
@@ -109,4 +109,71 @@
109
109
  見事 GC によって破棄されていました。
110
110
  ただ、[ソース](https://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Application.cs,323f71efbcb142b9)を確認してもリソースなどの参照を除外するようなコードが見当たりません。
111
111
  Shutdown() が呼ばれてから OnExit() メソッドが呼ばれるまでの間に何が起きているのか、
112
- そのコードはどこにあるのかを教えていただけないでしょうか。
112
+ そのコードはどこにあるのかを教えていただけないでしょうか。
113
+
114
+ 現在動作確認しているコードは次の通りです。
115
+ ```C#
116
+ namespace WpfApplication3
117
+ {
118
+ using System;
119
+ using System.Windows;
120
+
121
+ /// <summary>
122
+ /// App.xaml の相互作用ロジック
123
+ /// </summary>
124
+ public partial class App : Application
125
+ {
126
+ protected override void OnStartup(StartupEventArgs e)
127
+ {
128
+ base.OnStartup(e);
129
+
130
+ this.ShutdownMode = ShutdownMode.OnExplicitShutdown;
131
+
132
+ var w = new MainWindow();
133
+ // 弱参照でウォッチ
134
+ _r = new WeakReference<object>(w);
135
+
136
+ w.Show();
137
+ w.Close();
138
+ w = null;
139
+
140
+ // 生存確認
141
+ CheckAlive();
142
+
143
+ // 終了
144
+ this.Shutdown();
145
+ }
146
+
147
+ protected override void OnExit(ExitEventArgs e)
148
+ {
149
+ //base.OnExit(e);
150
+
151
+ // 生存確認
152
+ CheckAlive();
153
+ }
154
+
155
+ private void CheckAlive()
156
+ {
157
+ // GC を強制的におこなう
158
+ GC.Collect();
159
+ GC.WaitForPendingFinalizers();
160
+ GC.Collect();
161
+ MessageBox.Show("GC.Collect() しました。");
162
+
163
+ // 生存確認
164
+ object obj;
165
+ if (_r.TryGetTarget(out obj))
166
+ {
167
+ MessageBox.Show(obj + " を参照中。");
168
+ obj = null;
169
+ }
170
+ else
171
+ {
172
+ MessageBox.Show("参照していません。");
173
+ }
174
+ }
175
+
176
+ private WeakReference<object> _r;
177
+ }
178
+ }
179
+ ```

7

Shutdown() メソッドに関する補足を追記。

2019/03/22 00:07

投稿

twyujiro15
twyujiro15

スコア217

title CHANGED
File without changes
body CHANGED
@@ -104,4 +104,9 @@
104
104
  ただし、Window.Close() メソッドが呼ばれるとそのコレクションから参照が削除されます。[参考ソース](https://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Window.cs,4631)
105
105
  よって、App.Windows プロパティに関しては Close() メソッドさえ呼べば問題ないということがわかりました。
106
106
 
107
- ---
107
+ ---
108
+ App.Shutdown() を呼んだ後、OnExit() メソッドをオーバーライドして参照を確認したところ、
109
+ 見事 GC によって破棄されていました。
110
+ ただ、[ソース](https://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Application.cs,323f71efbcb142b9)を確認してもリソースなどの参照を除外するようなコードが見当たりません。
111
+ Shutdown() が呼ばれてから OnExit() メソッドが呼ばれるまでの間に何が起きているのか、
112
+ そのコードはどこにあるのかを教えていただけないでしょうか。

6

補足について修正。

2019/03/22 00:00

投稿

twyujiro15
twyujiro15

スコア217

title CHANGED
File without changes
body CHANGED
@@ -98,6 +98,10 @@
98
98
  一体誰がどこで MainWindow を参照し、GC を妨げているのか教えてください。
99
99
 
100
100
  ---
101
- 調査している中で、App.Windows プロパティが怪しそうだとうことがわかってきました。
101
+ 調査している中で、App.Windows プロパティが怪しい思いました、そうではありせんでした。
102
102
  このプロパティは「アプリケーションでインスタンス化されたウィンドウを取得」できるようです。
103
- このコレクションは読み取り専用で、クリアしたり各要素に null を代入したりすることができないようです。
103
+ このコレクションは読み取り専用で、クリアしたり各要素に null を代入したりすることができないようです。
104
+ ただし、Window.Close() メソッドが呼ばれるとそのコレクションから参照が削除されます。[参考ソース](https://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Window.cs,4631)
105
+ よって、App.Windows プロパティに関しては Close() メソッドさえ呼べば問題ないということがわかりました。
106
+
107
+ ---

5

App.Windows プロパティについて追記。

2019/03/21 23:30

投稿

twyujiro15
twyujiro15

スコア217

title CHANGED
File without changes
body CHANGED
@@ -95,4 +95,9 @@
95
95
  ```
96
96
  というように、表示すらしないようにしても GC されないようです。
97
97
 
98
- 一体誰がどこで MainWindow を参照し、GC を妨げているのか教えてください。
98
+ 一体誰がどこで MainWindow を参照し、GC を妨げているのか教えてください。
99
+
100
+ ---
101
+ 調査している中で、App.Windows プロパティが怪しそうだということがわかってきました。
102
+ このプロパティは「アプリケーションでインスタンス化されたウィンドウを取得」できるようです。
103
+ このコレクションは読み取り専用で、クリアしたり各要素に null を代入したりすることができないようです。

4

this.MainWindow = null; の一文を追記。

2019/03/20 07:15

投稿

twyujiro15
twyujiro15

スコア217

title CHANGED
File without changes
body CHANGED
@@ -44,6 +44,7 @@
44
44
  w.Show();
45
45
  w.Close();
46
46
  w = null;
47
+ this.MainWindow = null;
47
48
 
48
49
  // GC を強制的におこなう
49
50
  GC.Collect();

3

ファイナライザ終了オブジェクトも GC するように修正。

2019/03/20 07:07

投稿

twyujiro15
twyujiro15

スコア217

title CHANGED
File without changes
body CHANGED
@@ -48,6 +48,7 @@
48
48
  // GC を強制的におこなう
49
49
  GC.Collect();
50
50
  GC.WaitForPendingFinalizers();
51
+ GC.Collect();
51
52
  MessageBox.Show("GC.Collect() しました。");
52
53
 
53
54
  // 生存確認

2

GC でファイナライザの終了を待機するように修正。

2019/03/20 06:51

投稿

twyujiro15
twyujiro15

スコア217

title CHANGED
File without changes
body CHANGED
@@ -47,6 +47,7 @@
47
47
 
48
48
  // GC を強制的におこなう
49
49
  GC.Collect();
50
+ GC.WaitForPendingFinalizers();
50
51
  MessageBox.Show("GC.Collect() しました。");
51
52
 
52
53
  // 生存確認

1

表示しなくても同じ現象となることを追記。

2019/03/20 05:36

投稿

twyujiro15
twyujiro15

スコア217

title CHANGED
File without changes
body CHANGED
@@ -84,4 +84,12 @@
84
84
 
85
85
  あっさり GC してくれると思っていましたが、
86
86
  それほど単純ではないようです。
87
+
88
+ さらに上記のコードの内、
89
+ ```C#
90
+ //w.Show();
91
+ //w.Close();
92
+ ```
93
+ というように、表示すらしないようにしても GC されないようです。
94
+
87
95
  一体誰がどこで MainWindow を参照し、GC を妨げているのか教えてください。