- PR -

BeginInvokeで実行した処理の中断

1
投稿者投稿内容
Touch
会議室デビュー日: 2006/03/25
投稿数: 7
投稿日時: 2006-05-13 18:22
VB.NET(VS2003)で開発をしています。

BeginInvokeで実行した処理の中断について疑問があります。
ご教示ください。

以下ソースです(新規プロジェクトでForm2を追加し、Form1にはButtonを1個追加)。
----------------
■Form1
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
 Dim f As Form2
 Try
  f = New Form2
  f.ShowDialog(Me)
 Finally
  If Not f Is Nothing Then
   f.Dispose()
  End If
 End Try
End Sub

■Form2
Delegate Sub TestMethodDelegate()
Private _ar As IAsyncResult
Private _testMethodDelegateInstance As TestMethodDelegate

Private _abort As Boolean = False

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
 _testMethodDelegateInstance = New TestMethodDelegate(AddressOf TestMethod)
 _ar = _testMethodDelegateInstance.BeginInvoke(New AsyncCallback(AddressOf TestMethodCallback), Nothing)
End Sub

Private Sub TestMethod()
 Dim i As Integer = 0
 Do While _abort = False And i < 10
  Console.WriteLine(i)
  i += 1
  System.Threading.Thread.Sleep(10000)
 Loop
End Sub

Private Sub TestMethodCallback(ByVal ar As IAsyncResult)
 _testMethodDelegateInstance.EndInvoke(ar)
 Console.WriteLine("終了")
End Sub

Private Sub Form1_Closing(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles MyBase.Closing
 _abort = True
End Sub
----------------
・Button1がクリックされたらForm2を表示
・Form2ではLoad時にBeginInvokeによりバックグラウンドで処理(TestMethod)を開始
・TestMethodの処理が終了したらコールバックメソッド(TestMethodCallback)によりConsoleに「終了」を出力
・Form2が閉じられたらTestMethodの処理を中断

ということをしています。
中断は_abortフラグで判断するようにしましたが、ここで疑問が沸きました。

TestMethodが終了してからForm2を閉じられる分には問題ないと思うのですが、
TestMethodの終了前に閉じられた場合、TestMethodCallbackが呼ばれる前に
Form2のインスタンスが破棄されるように思います。

ですが、実際にはForm1でf.Dispose()が呼び出された後に「終了」がきちんと
表示されます。

この動きはどう理解すればいいのでしょうか?
「たまたま動いてしまっている」のであれば、どう実装すべきでしょうか?
よろしくお願いします。
なちゃ
ぬし
会議室デビュー日: 2003/06/11
投稿数: 872
投稿日時: 2006-05-13 22:19
引用:

Touchさんの書き込み (2006-05-13 18:22) より:
TestMethodが終了してからForm2を閉じられる分には問題ないと思うのですが、
TestMethodの終了前に閉じられた場合、TestMethodCallbackが呼ばれる前に
Form2のインスタンスが破棄されるように思います。

ですが、実際にはForm1でf.Dispose()が呼び出された後に「終了」がきちんと
表示されます。


どんな点を疑問に思っていますか?
フォームが破棄された後だと、コールバックが呼び出されないはず、と思っていますか?
フォームが破棄された後だと、Consoleへの書き込みはできないはず、と思っていますか?
あるいは、フォームが破棄された後だと、フォームのメソッドは実行できないはず、だと思っていますか?

Touch
会議室デビュー日: 2006/03/25
投稿数: 7
投稿日時: 2006-05-14 00:20
引用:

フォームが破棄された後だと、コールバックが呼び出されないはず、と思っていますか?


この点が疑問です。

Form2のインスタンスがどこからも参照されなくなったら、どこかのタイミングでガベージコレクタに回収される…という理解は合っていますよね?
とすると、うまく動いている理由を考えると、

・回収される前にTestMethod終了→TestMethodCallbackの処理が行われて、たまたまうまく動いてしまっている?
・BeginInvokeによる実行は、実は誰かがForm2のインスタンスの参照を保持しているので、TestMethodCallbackが呼び出されるまでは回収はされない?

のどちらかなんじゃないかな…?と思っていますが、ここで詰まってしまいました。
なちゃ
ぬし
会議室デビュー日: 2003/06/11
投稿数: 872
投稿日時: 2006-05-14 01:40
引用:

Touchさんの書き込み (2006-05-14 00:20) より:
・回収される前にTestMethod終了→TestMethodCallbackの処理が行われて、たまたまうまく動いてしまっている?
・BeginInvokeによる実行は、実は誰かがForm2のインスタンスの参照を保持しているので、TestMethodCallbackが呼び出されるまでは回収はされない?

のどちらかなんじゃないかな…?と思っていますが、ここで詰まってしまいました。


TestMethodCallbackはデリゲートとして渡しています。
このデリゲートはコールバックで必要ですから、BeginInvokeの処理の過程で内部的に保持されているでしょう。
デリゲートのインスタンス(への参照)が生きていますから、呼び出し対象のメソッドの属するインスタンスへの参照も生きています。

※こういうことが知りたい?
Touch
会議室デビュー日: 2006/03/25
投稿数: 7
投稿日時: 2006-05-14 10:17
引用:

なちゃさんの書き込み (2006-05-14 01:40) より:
TestMethodCallbackはデリゲートとして渡しています。
このデリゲートはコールバックで必要ですから、BeginInvokeの処理の過程で内部的に保持されているでしょう。
デリゲートのインスタンス(への参照)が生きていますから、呼び出し対象のメソッドの属するインスタンスへの参照も生きています。

※こういうことが知りたい?



なるほど…TestMethodCallbackへの参照を保持していなければ呼び出しはできませんよね。
でも、それでもやっぱり回収されてしまう可能性があるのでは…という疑問があります。

ガベージコレクションの考え方ですが、
-----
ObjMainがObjAを作成し、ObjAがObjBを作成する。
この時ObjBにはObjAへの参照を渡している(ObjAとObjBは循環参照になる)。
ここでObjMainがObjAへの参照を解放したら、ObjAとObjBは宙ぶらりんの状態になり、やがてガベージコレクタに回収される(.NETのガベージコレクタが循環参照を検知して回収する)。
-----
と理解しています。
示したコードもこれと同じ構図になるように思える(ObjMain=Form1/ObjA=Form2/ObjB=_testMethodDelegateInstance?)のですが…。
だとすると、やはりたまたま動いているだけなのでは?と思ってしまいます。
Bob
常連さん
会議室デビュー日: 2006/03/23
投稿数: 31
投稿日時: 2006-05-15 20:10
引用:

なるほど…TestMethodCallbackへの参照を保持していなければ呼び出しはできませんよね。
でも、それでもやっぱり回収されてしまう可能性があるのでは…という疑問があります。

ガベージコレクションの考え方ですが、


と参照がなくなる時点に、GCにてCollectされるでしょうか?
実際、GC.Collect()は参照がなくなったら、すぐに動作するのではなく、明示的に行わない限り、必要な場合のみ行われます。

後、BeginInvokeで起したThreadは実にThreadPoolに管理されており、自分のアプリ側にCallbackへの参照を破棄しても、ThreadPoolの管理Threadはちゃんともっていると思います。
Touch
会議室デビュー日: 2006/03/25
投稿数: 7
投稿日時: 2006-05-16 20:15
引用:

Bobさんの書き込み (2006-05-15 20:10) より:
と参照がなくなる時点に、GCにてCollectされるでしょうか?
実際、GC.Collect()は参照がなくなったら、すぐに動作するのではなく、明示的に行わない限り、必要な場合のみ行われます。

後、BeginInvokeで起したThreadは実にThreadPoolに管理されており、自分のアプリ側にCallbackへの参照を破棄しても、ThreadPoolの管理Threadはちゃんともっていると思います。


参照が無くなった後にGC.Collect()してみましたが、Callbackも呼ばれて問題なく動作しました。
引き続きThreadPool等についても調べてみようと思います。

なちゃさん、Bobさん、ありがとうございました。
1

スキルアップ/キャリアアップ(JOB@IT)