- 題名: 別スレッドからのフォーム操作中にフォームを閉じると
- 日時: 2008/10/08 2:25:49
- ID: 23157
- この記事の返信元:
- (なし)
- この記事への返信:
- [23172] Re[1]: 別スレッドからのフォーム操作中にフォームを閉じると2008/10/12 19:43:24
- ツリーを表示
VB2008で試してみたのですが、FormのIsDisposedプロパティがFalseなのに ObjectDisposedExceptionがスローされてしまうタイミングがあるようです (Overrides OnFormClosingメソッド内で1000ミリ秒時間待ちをしている部分)。 <追記> ↑OnFormClosingメソッド終了後にInvokeメソッドが実行されるので、 「タイミング」という表現は不適切だったかもしれません。m(_ _)m </追記> ありきたりな対策しか思いつきませんでしたが、 フラグ変数で判定すれば回避できるようです。 Imports System.Threading ' スタートアップフォームに指定 Public Class DisposeTestLauncher Inherits Form Protected Overrides Sub OnShown(ByVal e As System.EventArgs) Me.Text = "ただのランチャー" Dim frm As New DisposeTestForm frm.Show() MyBase.OnShown(e) End Sub End Class Public Class DisposeTestForm Inherits Form Dim m_are As New AutoResetEvent(False) Dim m_closing As Boolean = False Protected Overrides Sub OnLoad(ByVal e As System.EventArgs) Me.Text = "このフォームを閉じる" ThreadPool.QueueUserWorkItem(AddressOf Me.DoWork, Nothing) MyBase.OnLoad(e) End Sub Private Sub DoWork(ByVal o As Object) m_are.WaitOne() ' Formを閉じるとここに来る Me.Disp(o) End Sub Private Sub Disp(ByVal o As Object) If Me.IsDisposed = True Then Return End If If Me.InvokeRequired = True Then 'MsgBox(Me.IsDisposed.ToString) ' ← 結果はFalse If Me.IsDisposed = False Then Me.Invoke(New Action(Of Object)(AddressOf Me.Disp), o) ' ↓これならOKだった 'If Me.m_closing = False Then Me.Invoke(New Action(Of Object)(AddressOf Me.Disp), o) Else Me.Text += "−追加" End If End Sub Protected Overrides Sub OnFormClosing(ByVal e As System.Windows.Forms.FormClosingEventArgs) m_closing = True m_are.Set() 'MsgBox(Me.IsDisposed.ToString)' ← 結果はFalse ' ↓ObjectDisposedExceptionを発生しやすくするための時間待ち Thread.Sleep(1000) MyBase.OnFormClosing(e) End Sub End Class
コードをバージョンアップしてみました。 System.Threading.TimerとAsyncOperation.Postメソッドを使ったところ、 Formを開く→閉じるを1万回繰り返してもエラー数0でした。(実行時間は40分程度です) Imports System.ComponentModel Imports System.Threading ' スタートアップFormに指定 Public Class DisposeTestLancher_ver2 Inherits Form Dim WithEvents Button1 As New Button ' Formを表示して、一定時間経過後閉じるためのTimer Dim WithEvents Timer1 As New System.Windows.Forms.Timer Dim m_close As Boolean = False Dim m_count As Integer = 0 Dim m_errorcount As Integer = 0 Dim m_frm As Form Dim m_rand As New Random(Environment.TickCount Mod 1000) Protected Overrides Sub OnLoad(ByVal e As System.EventArgs) Me.Text = "スタートアップForm" Me.Button1.Text = "Start" Me.Controls.Add(Me.Button1) Me.Timer1.Enabled = False MyBase.OnLoad(e) End Sub Private Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click ' Formを1万回表示してエラー回数を測定 Me.m_close = False Me.Timer1.Interval = 10 Me.Timer1.Enabled = True End Sub Private Sub Timer1_Tick(ByVal sender As Object, ByVal e As System.EventArgs) Handles Timer1.Tick If Me.m_close = False Then Me.m_frm = New DisposeTestForm_ver2 Me.m_frm.Show() Me.Timer1.Interval = Me.m_rand.Next(80, 300) ' Formを閉じるまでの時間 Else Try If Me.m_frm IsNot Nothing Then m_frm.Close() Catch ex As InvalidOperationException ' ObjectDisposedExceptionもここに来る Me.m_errorcount += 1 Finally Me.m_count += 1 End Try ' 100回毎に経過表示 If Me.m_count Mod 100 = 0 Then Console.WriteLine("{0}回 - エラー数 {1}", Me.m_count, Me.m_errorcount) Me.Timer1.Interval = 10 ' 次のFormはすぐ表示する If Me.m_count = 10000 Then Me.Timer1.Enabled = False ' 1万回で終了 End If Me.m_close = Not Me.m_close End Sub End Class ' 1万回表示されるForm Public Class DisposeTestForm_ver2 Inherits Form Dim m_ao As AsyncOperation = AsyncOperationManager.CreateOperation(Me) Dim m_IsBusy As Boolean = False Dim Timer1 As System.Threading.Timer Dim AddressOf_SetText As New SendOrPostCallback(AddressOf Me.SetText) Dim m_closing As Boolean = False Protected Overrides Sub OnLoad(ByVal e As System.EventArgs) Me.Timer1 = New System.Threading.Timer(AddressOf Me.DoWork, "あああ", 0, 50) Me.BackColor = Color.Blue MyBase.OnLoad(e) End Sub Private Sub DoWork(ByVal o As Object) Me.m_IsBusy = True ' デッドロック回避のため、非同期呼び出しを使用する If Me.m_closing = False Then m_ao.Post(Me.AddressOf_SetText, o) End If Me.m_IsBusy = False End Sub Private Sub SetText(ByVal o As Object) If Me.m_closing = True Then Return Me.Text = o.ToString Me.Show() ' ←閉じた後なら、ObjectDisposedExceptionになる End Sub Protected Overrides Sub OnFormClosing(ByVal e As System.Windows.Forms.FormClosingEventArgs) Me.m_closing = True Me.Timer1.Dispose() ' Timerのコールバックメソッド終了を待つ(フラグを使った苦し紛れの処理) Do While Me.m_IsBusy = True Thread.Sleep(10) Loop Me.m_ao.OperationCompleted() MyBase.OnFormClosing(e) End Sub End Class
分類:[.NET]
お世話になります。
早速ですが、別スレッドからのフォーム操作中にフォームを閉じると
ObjectDisposedException例外が発生し、対策に悩んでおります。
毎回発生するわけではなくタイミングです。
状況
フォームに配置したWMPコンポーネントの再生中の時間を監視したいので、
System.Threading.TimerでWMPコンポーネントのcurrentPositionを取得しました。
この取得は「別スレッドからのフォーム操作」なので、Invokeを使用して下記のソースを書きました。
--------------------------------------------------------------------------------------------------------------
private delegate double GetCurrentPositionDelegate();
public double GetCurrentPosition()
{
Console.WriteLine("--- GetCurrentPosition start:[{0}] --- ", Thread.CurrentThread.ManagedThreadId);
double pos;
if (InvokeRequired)
{
pos = (double)Invoke(new GetCurrentPositionDelegate(GetCurrentPosition));
}
else
{
pos = axWindowsMediaPlayer1.Ctlcontrols.currentPosition;
}
Console.WriteLine("--- GetCurrentPosition end :[{0}] --- ", Thread.CurrentThread.ManagedThreadId);
return pos;
}
// 状況トレース用
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
System.Console.WriteLine("--- Form1_FormClosing:[{0}] --- ", Thread.CurrentThread.ManagedThreadId);
}
// 状況トレース用
protected override void Dispose(bool disposing)
{
Console.WriteLine("--- Dispose start:[{0}] --- ", Thread.CurrentThread.ManagedThreadId);
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
Console.WriteLine("--- Dispose end :[{0}] --- ", Thread.CurrentThread.ManagedThreadId);
}
--------------------------------------------------------------------------------------------------------------
デバッグ出力
スレッド 0xc84 はコード 0 (0x0) で終了しました。
スレッド 0xe6c はコード 0 (0x0) で終了しました。
スレッド 0x9e0 はコード 0 (0x0) で終了しました。
--- GetCurrentPosition start:[10] ---
--- GetCurrentPosition start:[8] ---
--- GetCurrentPosition end :[8] ---
--- GetCurrentPosition end :[10] ---
--- GetCurrentPosition start:[10] ---
'System.ObjectDisposedException' の初回例外が System.Windows.Forms.dll で発生しました。
--- Form1_FormClosing:[8] ---
--- Dispose start:[8] ---
---破棄されたオブジェクトにアクセスできません。
オブジェクト名 'Form1' です。:
場所 System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous)
場所 System.Windows.Forms.Control.Invoke(Delegate method, Object[] args)
場所 System.Windows.Forms.Control.Invoke(Delegate method)
場所 Sample.Form1.GetCurrentPosition() 場所 E:\WORKSPASE\C#\2008_10\Sample\Sample\Form1.cs:行 50
場所 Sample.Form1.TimerThreadProc(Object state) 場所 E:\WORKSPASE\C#\2008_10\Sample\Sample\Form1.cs:行 35
--- Dispose end :[8] ---
スレッド 0x11ec はコード 0 (0x0) で終了しました。
スレッド 0x1354 はコード 0 (0x0) で終了しました。
プログラム '[4028] Sample.vshost.exe: マネージ' はコード 0 (0x0) で終了しました。
不明点
デバッグ出力から、GetCurrentPositionはタイマースレッドでの実行後にメインスレッドで実行しています。
これはInvokeを介して行われているので問題ありません。
問題は「タイマースレッドでの実行」と「メインスレッドでの実行」の間にフォームを閉じたので、
フォームが破棄され「メインスレッドでの実行」を行う際にObjectDisposedExceptionが発生することです。
フォームが破棄されているのでIsDisposedなどでのチェックができない為、対策が出来ない状況です。
記述したソースはネットを参考に記載しており、あちこちで見かけたので一般的な書き方だと思うのですが、
ObjectDisposedExceptionについて記載しているサイトを見つけることが出来ませんでした。
フォームを閉じるときに発生する例外なので、try〜catchで握り潰すことも考慮していますが、
出来れば何らかの対策を行いたいと思っております。
よろしくお願いいたします