none
ThreadPool.ThreadPool.QueueUserWorkItemを用いて非同期でメソッドを呼び出した時、そのメソッドを持つオブジェクトが無いときの動作について質問 RRS feed

  • 質問

  • ThreadPool.ThreadPool.QueueUserWorkItemを用いて非同期でメソッドを呼び出した時について、質問があります。

    下記コードのような形で非同期処理を呼び出し、呼び出し先のメソッドを持つオブジェクトがすでに存在しないような状況の場合、非同期処理はどのような動作を示すのでしょうか?

    非同期処理の対象となっているメソッドを保有するクラスは、非同期処理に予約されていてもガベージコレクションの対象になるのでしょうか?

    あるいは、こういった呼び出し元がそのスレッドにてもう使われていない可能性がある場合、非同期処理終了コールバックを待つのがセオリーなのでしょうか?

    // 呼び出し元
    public void CallSubRun()
    {
    Sub sub = new Sub();
    sub.Run();
    }

    // 呼び出し先クラス
    class Sub
    {
    private object objA = new Object();

    public void Run()
    {
    ThreadPool.QueueUserWorkItem(AsyncMethod);
    }

    private void AsyncMethod()
    {
    Thread.Sleep(1000);
    Console.WriteLine(objA.ToString());
    }
    }

    環境:

    Visual Studio 2013 Professional

    .net framework 3.5

    2014年6月20日 6:57

回答

  • Disposeはされますね。

    で、この場合BeginInvokeが確実に実行できるとは断言できなくなります。何らかの手段でformが破棄済みかどうか取得して破棄済みでないと判定されても、次にBeginInvokeを呼び出すときにはformが破棄されている可能性があるからです。

    この問題に対する完全な解決を私は知りません。

    • 回答としてマーク h--s 2014年6月20日 8:55
    • 回答としてマークされていない h--s 2014年6月20日 8:55
    • 回答としてマーク h--s 2014年6月20日 8:56
    2014年6月20日 8:39

すべての返信

  • ThreadPool.QueueUserWorkItemに渡されるデリゲートは、インスタンスメソッドの場合そのインスタンス自体に対する参照を保持します。

    で、そのデリゲートはThreadPoolによってデリゲートの処理の完了まで維持されますから、SubクラスのインスタンスはAsyncMethodが完了するまで参照が存続します。当然、その間GCが片付けることはありません。

    もちろん、Subが保持されている間はSubが持っているobjAも保持されます。

    • 回答としてマーク h--s 2014年6月20日 7:48
    • 回答としてマークされていない h--s 2014年6月20日 7:50
    2014年6月20日 7:16
  • スレッドプールにキューされた非同期処理は、内部的にインスタンスを保持しているためGCの対象にならないということですね。

    理解出来ました。

    仮に呼び出し元のメソッドを持っており、そしてそれ呼び出すのクラスがFormを継承したクラスのFormClosingイベントの場合、どのような動作になるのでしょうか?

    public class CallerForm : Form
    	
    	public void CallerForm_FormClosing(object sender, FormClosingEventArgs e)
    	{
    		Sub sub = new Sub(this);
    		sub.Run();
    	}
    	
    	public UpdateMessage(string message)
    	{
    		if (this.IsHandleCreated)
    		{
    			textbox1.Text = message;
    		}
    	}
    }
    
    class Sub
    {
    	private CallerForm form;
    	
    	public Sub(CallerForm form)
    	{
    		this.form = form;
    	}
    	
    	public void Run()
    	{
    		ThreadPool.QueueUserWorkItem(AsyncMethod);
    	}
    	
    	private void AsyncMethod()
    	{
    		Thread.Sleep(1000);
    		form.BeginInvoke(new Action(form.UpdateMessage("test")));
    	}
    }
    

    この場合は、フォームが閉じることでフォームの持つリソースが解放されるのでしょうか?

    また、開放された場合、IsHandleCreatedプロパティのチェックで解放されたリソースに対するアクセスを回避することができるのでしょうか?

    2014年6月20日 8:09
  • Disposeはされますね。

    で、この場合BeginInvokeが確実に実行できるとは断言できなくなります。何らかの手段でformが破棄済みかどうか取得して破棄済みでないと判定されても、次にBeginInvokeを呼び出すときにはformが破棄されている可能性があるからです。

    この問題に対する完全な解決を私は知りません。

    • 回答としてマーク h--s 2014年6月20日 8:55
    • 回答としてマークされていない h--s 2014年6月20日 8:55
    • 回答としてマーク h--s 2014年6月20日 8:56
    2014年6月20日 8:39
  • form.BeginInvokeの実行のためにはハンドルが必要ですので、IsHandleCreatedのチェックはその前に必要になってしまいますねぇ。
    2014年6月20日 8:41
  • 回答有難うございます。

    フォーム終了時に非同期処理にて何かを実行し、万が一エラーがあった場合フォーム要素に情報を表示するなどの処理をしたかったのですが……

    こういった場合はFormClosingにて一度閉じるを拒否しておき、非同期処理のコールバックを確認してから改めて閉じる、といった処理を実装すればいいんでしょうね。

    処理が猥雑になり、後々のコード管理者に手間を掛けそうなので、フォームクローズ処理とは別にクローズ処理を実行するボタンを設け、その処理を経てクローズ状態に移行しない限りフォームを閉じられないようなUI設計にして対処しようと思います。

    早い回答、ありがとうございました!

    2014年6月20日 8:56