- 題名: 別スレッドからのフォーム操作中にフォームを閉じると
- 日時: 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で握り潰すことも考慮していますが、
出来れば何らかの対策を行いたいと思っております。
よろしくお願いいたします