none
執行緒打開子視窗,關閉時出現錯誤 RRS feed

  • 問題

  • 我在 主Form 設了一個 System.Timers.Timer 來輪詢判斷式,判斷式條件符合時開啟 Form2,
    希望 Form2 打開後可以獨佔最上層(和 ShowDialog 功能一樣),當輪詢判斷式符合關閉 Form2 時關閉 Form2,
    請問可以怎麼用?
    我打的程式會出現 TargetInvocationException 的錯誤。

    Form1:

    Public Class Form1
    
        Dim ob As Object
        Dim SystemTime1 As New System.Timers.Timer()
        Dim a As Integer
    
    
        Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            AddHandler SystemTime1.Elapsed, AddressOf Timer
            SystemTime1.Interval = 200
            SystemTime1.Enabled = True
            GC.KeepAlive(SystemTime1)
            ob = Form2
            ob.owner = Me
    
        End Sub
    
        Sub Timer()
            a += 1
            If a > 10 Then
                myUI(ob)  '<---這一行會出現 TargetInvocationException 的錯誤。
                a = 0
            Else
                ob = Form2
                If Not ob.Visible Then
                    myUI1(ob)
                End If
            End If
        End Sub
    
        Private Delegate Sub myUICallBack(ByVal c As Object)
        Private Sub myUI(ByVal c As Object)
            If InvokeRequired Then
                Dim myUpdate As myUICallBack = New myUICallBack(AddressOf myUI)
                BeginInvoke(myUpdate, c)
            Else
                c.Dispose()
            End If
        End Sub
    
        Private Sub myUI1(ByVal c As Object)
            If InvokeRequired Then
                Dim myUpdate As myUICallBack = New myUICallBack(AddressOf myUI1)
                BeginInvoke(myUpdate, c)
            Else
                c.showdialog()
            End If
        End Sub
    
    End Class

    Form2:

    Public Class Form2
    
        Dim SystemTime1 As New System.Timers.Timer()
    
        Private Sub Form2_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            AddHandler SystemTime1.Elapsed, AddressOf Timer  'System.Timer 設定
            SystemTime1.Interval = 700
            SystemTime1.Enabled = True
            GC.KeepAlive(SystemTime1)
        End Sub
    
        Dim b As Integer = 0
        Sub timer()
            Dim a As String = "請稍候"
    
            b += 1
            If b > 10 Then
                myUI(a & str(b), Label1)
                b = 0
            Else
                myUI(a & str(b), Label1)
            End If
        End Sub
    
        Function str(ByVal number As Integer) As String
            Return StrDup(number, ".")
        End Function
    
    
        Private Delegate Sub myUICallBack(ByVal myStr As String, ByVal c As Control)
        Private Sub myUI(ByVal myStr As String, ByVal c As Control)
            If InvokeRequired Then
                Dim myUpdate As myUICallBack = New myUICallBack(AddressOf myUI)
                BeginInvoke(myUpdate, myStr, c)
            Else
                c.Text = myStr
            End If
        End Sub
    End Class


    • 已編輯 C.Kevin 2013年1月2日 上午 09:32
    2013年1月2日 上午 09:30

解答

  • 我猜你大概沒有很清楚我講的概念, 可能是說的不清楚, 不僅是要使用建構函式產生特定執行個體, 也要對著那個執行個體明確操作. 你可以試試以下的 Code, 由於我也不是很明白你程式碼的目的, 所以先改部份讓你試試. 你可以試著調Interval 到 100, 在我的電腦上是不會出錯的

    基本上, 我很不贊成在 Visual Basic 中直接對著 Form 的類別名稱操作, 簡單地說, 當你寫 Form2.Show(), 為何會跑出 Form2 , 原因在於 Visual Basic 會先判斷這個執行緒中有沒有 Form2 類別的執行個體, 如果沒有, 它就建一個出來, 但這也是雷之所在, 當你在多緒的時候, 很難避免掉倒底現在這個 From2 指的是那一個 From2 的困擾.

    所以我的習慣通常是會先使用建構函式產生執行個體 (除非是寫很簡單的 Sample Code), 然後明確對這個執行個體操作.

    Public Class Form1
    	Dim ob As Object
    	Dim SystemTime1 As New System.Timers.Timer()
    	Dim a As Integer
    	Private frm As Form2
    
    	Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
    		AddHandler SystemTime1.Elapsed, AddressOf Timer
    		SystemTime1.Interval = 200
    		SystemTime1.Enabled = True
    		GC.KeepAlive(SystemTime1)
    		frm = New Form2()
    		ob = frm
    		ob.owner = Me
    
    	End Sub
    
    	Sub Timer()
    		a += 1
    		If a > 10 Then
    			myUI(ob)  '<---這一行會出現 TargetInvocationException 的錯誤。
    			a = 0
    		Else
    			frm = New Form2()
    			ob = frm
    			If Not ob.Visible Then
    				myUI1(ob)
    			End If
    		End If
    	End Sub
    
    	Private Delegate Sub myUICallBack(ByVal c As Object)
    	Private Sub myUI(ByVal c As Object)
    		If InvokeRequired Then
    			Dim myUpdate As myUICallBack = New myUICallBack(AddressOf myUI)
    			BeginInvoke(myUpdate, c)
    		Else
    			c.Dispose()
    		End If
    	End Sub
    
    	Private Sub myUI1(ByVal c As Object)
    		If InvokeRequired Then
    			Dim myUpdate As myUICallBack = New myUICallBack(AddressOf myUI1)
    			BeginInvoke(myUpdate, c)
    		Else
    			c.showdialog()
    		End If
    	End Sub
    End Class
    


    在現實生活中,你和誰在一起的確很重要,甚至能改變你的成長軌跡,決定你的人生成敗。 和什麼樣的人在一起,就會有什麼樣的人生。 和勤奮的人在一起,你不會懶惰; 和積極的人在一起,你不會消沈; 與智者同行,你會不同凡響; 與高人為伍,你能登上巔峰。

    2013年1月3日 上午 04:00
    版主

所有回覆

  • 您好,
    請問請錯誤是從VS.NET執行時所發生的Exception嗎?
    直接執行Exe也會發生Exception嗎?

    以上說明若有錯誤請指教,謝謝。
    亂馬客blog: http://www.dotblogs.com.tw/rainmaker/

    2013年1月2日 上午 09:59
  • Visual Basic 的 Form 類別有很多奇特的特性, 比方你只要建立一個 Form2 Class, 就可以直接 Form2.Show() (這是一種編譯器魔法)

    你不覺得這是個很奇怪的事嗎? 真正原始的寫法應該是

    Dim frm as new Form2

    frm.Show()

    尤其在多執行緒的狀態, 你用 Form2.Show 或是直接去呼叫 Form2 讓編譯器魔法發生,  就很容易踩到雷.

    所以, 先改成我上面的寫法再測試, 另一個問題是, 為什麼你的自訂 Delegate 要用 Object 當參數型別?


    在現實生活中,你和誰在一起的確很重要,甚至能改變你的成長軌跡,決定你的人生成敗。 和什麼樣的人在一起,就會有什麼樣的人生。 和勤奮的人在一起,你不會懶惰; 和積極的人在一起,你不會消沈; 與智者同行,你會不同凡響; 與高人為伍,你能登上巔峰。

    2013年1月2日 下午 01:33
    版主
  • To 亂馬客: 

    是在 VB.NET 裡時出現錯誤的,後來我把 Form1.Load 裡的 System.Timers.Timer 速度由 200ms 調慢至 400ms 情況有好很多,
    原因為何還是沒辦法找到,也許是 Dispose 與 ShowDialog 間格不能太靠近,釋放資源可能需要一點時間...

    請問,為什麼有時會需要執行 \bin\Debug 裡的 exe 檔來判斷程式是否正常呢?
    是因為 VB.NET 的 F5 偵錯有瑕疵嗎? 還是只要exe 檔執行沒問題就好這樣呢?

    2013年1月3日 上午 02:07
  • 您好,
    因為小弟有在網路上看到這篇「Why is TargetInvocationException treated as uncaught by the IDE?」,
    所以才想問看看您發生錯誤的情況。

    您可試看看版大所說的方式調整看看哦!


    以上說明若有錯誤請指教,謝謝。
    亂馬客blog: http://www.dotblogs.com.tw/rainmaker/


    • 已編輯 亂馬客 2013年1月3日 上午 02:30
    2013年1月3日 上午 02:11
  • To Bill Chung:

    在 Delegate 中用 Object 當參數型別也是怕會出現 執行的對象與宣告對象(Form2) 不同的情況,之前也有想過用 Form2 做參數型別,
    因為還不很熟 Delegate、ByVal、ByRef、宣告 Form,想說 Object 像是一個容器一樣,給 Object 什麼東西,Object 就會成為什麼才出此下策。

    我剛剛試了 Dim ob As Object 與 Dim frm As New Form2 兩個都OK,測試條件是在 Form1.Load 的 Timer 為 400ms 情形下,
    MSDN 沒有做很多的解釋(或我根本看不懂),直翻 
    TargetInvocationException 中文意思加上 Timer 時間延長,
    初步判斷可能是 Dispose 需要時間執行,資源還沒釋放完畢前再叫用就會發生錯誤,因為 Timer 時間調回 200ms 這兩種宣告方式都會出現一樣的錯誤。

    再來,

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            AddHandler SystemTime1.Elapsed, AddressOf Timer
            SystemTime1.Interval = 200
            SystemTime1.Enabled = True
            GC.KeepAlive(SystemTime1)
            ob = Form2
            ob.owner = Me
    
        End Sub
    
        Sub Timer()
            a += 1
            If a > 10 Then
                myUI(ob)  
                a = 0
            Else
                'ob = Form2  '<---這一行要放在 If 判斷式內
                If Not ob.Visible Then
                    ob = Form2
    myUI1(ob) End If End If End Sub

    意外發現 ob = Form2 要在判斷式內,如果在外面會出現不只 1 個 Form2 被叫出來,可是這裡就很奇怪了,
    ob = Form2 放在判斷式內,前一執行步驟為 Dispose,ob.Visible 應該會先出錯才對,所以這就是編譯器魔法了嗎?

    2013年1月3日 上午 02:48
  • 我猜你大概沒有很清楚我講的概念, 可能是說的不清楚, 不僅是要使用建構函式產生特定執行個體, 也要對著那個執行個體明確操作. 你可以試試以下的 Code, 由於我也不是很明白你程式碼的目的, 所以先改部份讓你試試. 你可以試著調Interval 到 100, 在我的電腦上是不會出錯的

    基本上, 我很不贊成在 Visual Basic 中直接對著 Form 的類別名稱操作, 簡單地說, 當你寫 Form2.Show(), 為何會跑出 Form2 , 原因在於 Visual Basic 會先判斷這個執行緒中有沒有 Form2 類別的執行個體, 如果沒有, 它就建一個出來, 但這也是雷之所在, 當你在多緒的時候, 很難避免掉倒底現在這個 From2 指的是那一個 From2 的困擾.

    所以我的習慣通常是會先使用建構函式產生執行個體 (除非是寫很簡單的 Sample Code), 然後明確對這個執行個體操作.

    Public Class Form1
    	Dim ob As Object
    	Dim SystemTime1 As New System.Timers.Timer()
    	Dim a As Integer
    	Private frm As Form2
    
    	Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
    		AddHandler SystemTime1.Elapsed, AddressOf Timer
    		SystemTime1.Interval = 200
    		SystemTime1.Enabled = True
    		GC.KeepAlive(SystemTime1)
    		frm = New Form2()
    		ob = frm
    		ob.owner = Me
    
    	End Sub
    
    	Sub Timer()
    		a += 1
    		If a > 10 Then
    			myUI(ob)  '<---這一行會出現 TargetInvocationException 的錯誤。
    			a = 0
    		Else
    			frm = New Form2()
    			ob = frm
    			If Not ob.Visible Then
    				myUI1(ob)
    			End If
    		End If
    	End Sub
    
    	Private Delegate Sub myUICallBack(ByVal c As Object)
    	Private Sub myUI(ByVal c As Object)
    		If InvokeRequired Then
    			Dim myUpdate As myUICallBack = New myUICallBack(AddressOf myUI)
    			BeginInvoke(myUpdate, c)
    		Else
    			c.Dispose()
    		End If
    	End Sub
    
    	Private Sub myUI1(ByVal c As Object)
    		If InvokeRequired Then
    			Dim myUpdate As myUICallBack = New myUICallBack(AddressOf myUI1)
    			BeginInvoke(myUpdate, c)
    		Else
    			c.showdialog()
    		End If
    	End Sub
    End Class
    


    在現實生活中,你和誰在一起的確很重要,甚至能改變你的成長軌跡,決定你的人生成敗。 和什麼樣的人在一起,就會有什麼樣的人生。 和勤奮的人在一起,你不會懶惰; 和積極的人在一起,你不會消沈; 與智者同行,你會不同凡響; 與高人為伍,你能登上巔峰。

    2013年1月3日 上午 04:00
    版主
  • 這陣子一直在做資料庫備份的問題,設計定時備份,但正在備份的同時使用者可能也正在使用程式,前陣子遇到壓縮資料庫時資料庫必須是被壓縮所獨佔,
    本想在資料庫讀寫的中間加入判斷式與Thread.Join,判斷需要壓縮時就進入Thread.Join 壓縮資料庫,後來想想這可能對我會增加除錯困難度作罷,
    我想當壓縮資料庫時只要資料庫連線動作暫停,壓縮資料庫就會沒有問題,於是我做了2個 Timer 輪詢,第一個 Timer 用作資料庫讀寫,
    第二個用做檢查資料庫是否達到備份標準,而這篇提問中的 Timer 指的就是第二個,當 Timer2 檢察符合備份條件時會將 Timer1 與自己(Timer2)暫停然後開始資料庫備份,
    備份資料庫假設會超過1分鐘,這段時間資料庫都不可以有讀寫動作,所以我也將 MainForm 畫面暫停更新,
    為了讓使用者了解當下不是當機而想到可以利用類似 MessageBox.Show 或 Form.ShowDialog 的功能,顯示於 MainForm 之上且獨佔操作,
    讓叫出來的視窗顯示"正在備份..."的跑馬燈,在這視窗消失以前都無法操作 MainForm,備份完再啟動 Timer1 和 Timer2。
    提問中的 Form1 迴圈是用來測試打開子視窗執行是否順利。

    Dim SystemTime1 As New System.Timers.Timer() Dim SystemTime2 As New System.Timers.Timer() Public BackUp As New BackUp_0106 '<---自建類別 Dim frm As New BackupWaiting '<---原Form2,顯示跑馬燈,程式內容同提問程式(Form2) Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load AddHandler SystemTime1.Elapsed, AddressOf Timer SystemTime1.Interval = 900 GC.KeepAlive(SystemTime1) AddHandler SystemTime2.Elapsed, AddressOf CheckBackup SystemTime2.Interval = 900 systemTime2.Enabled = True GC.KeepAlive(SystemTime2) End Sub Private Sub CheckBackup() If DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") > BackUp.Years Then SystemTime1.Enabled = False SystemTime2.Enabled = False If Not frm.Visible Then frm = BackupWaiting Form_Show(frm) '<---委派打開Form End If BackUp.TemperatureBackup() BackUp.AlmBackup() BackUp.DosingSetBackup() BackUp.ProductionInfoBackup() BackUp.UserFlowBackup() BackUp.UpdateYearTimeBackup() BackUp.UpdataLestBackupTime()
    Form_Dis(frm) '<---委派關閉Form SystemTime1.Enabled = True SystemTime2.Enabled = True Else

    SystemTimer1.Enabled = True

    End If End Sub

    Private Delegate Sub myUICallBack(ByVal c As BackupWaiting)
        Private Sub Form_Dis(ByVal c As BackupWaiting)
            If InvokeRequired Then
                Dim myUpdate As myUICallBack = New myUICallBack(AddressOf Form_Dis)
                BeginInvoke(myUpdate, c)
            Else
                c.Dispose()
            End If
        End Sub
       
        Private Sub Form_Show(ByVal c As BackupWaiting)
            If InvokeRequired Then
                Dim myUpdate As myUICallBack = New myUICallBack(AddressOf Form_Show)
                BeginInvoke(myUpdate, c)
            Else
                c.ShowDialog()
            End If
        End Sub

    我執行後 Form2 瘋狂的被打開,一直新增新視窗,沒有任何視窗被 Dispose 的感覺。

    Public Class Form1
    
        Dim SystemTime1 As New System.Timers.Timer()
        Dim a As Integer
        Private frm As Form2
    
        Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            AddHandler SystemTime1.Elapsed, AddressOf Timer
            SystemTime1.Interval = 100
            SystemTime1.Enabled = True
            GC.KeepAlive(SystemTime1)
            'frm = New Form2()
        End Sub
    
        Sub Timer()
            a += 1
            If a > 10 Then
                myUI(frm)
                a = 0
            Else
                frm = New Form2()
                If Not frm.Visible Then
                    'frm = New Form2()  <---這句放在判斷式裡情況也相同
                    myUI1(frm)
                End If
            End If
        End Sub
    
        Private Delegate Sub myUICallBack(ByVal c As Form2)
        Private Sub myUI(ByVal c As Form2)
            If InvokeRequired Then
                Dim myUpdate As myUICallBack = New myUICallBack(AddressOf myUI)
                BeginInvoke(myUpdate, c)
            Else
                c.Dispose()
            End If
        End Sub
    
        Private Sub myUI1(ByVal c As Form2)
            If InvokeRequired Then
                Dim myUpdate As myUICallBack = New myUICallBack(AddressOf myUI1)
                BeginInvoke(myUpdate, c)
            Else
                c.ShowDialog()
            End If
        End Sub
    End Class



    • 已編輯 C.Kevin 2013年1月4日 上午 12:55
    2013年1月3日 上午 08:22
  • To 亂馬客:

    可能 VB 2008 Express 沒有文中的 Debugging ,
     Tools -> Options -> Debugging -> General -> Enable Just My Code.


    • 已編輯 C.Kevin 2013年1月3日 上午 08:56
    2013年1月3日 上午 08:51