2011年12月19日 星期一

使用 Delegate 解決 [跨執行緒作業無效] 的問題!

今天在寫程式時遇到錯誤訊息
System.InvalidOperationException: 跨執行緒作業無效: 存取控制項 'txtLog' 時所使用的執行緒與建立控制項的執行緒不同。

這個問題很明顯是在多執行緒的物件中,要在另一個執行緒物件裡做動作是會有問題的
我寫了一個小程式重現一下這個問題,
程式很簡單,畫面如下


會出現錯誤的程式碼如下:
Public Class Form3
    Dim _testTimer As Timers.Timer
    Dim _timerInterval As Integer = 3
    Dim _timerCount As Integer

    Private Sub btnStopTimer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnStopTimer.Click
        ''停止時間
        Me._testTimer.Stop()
    End Sub
    Private Sub btnStartTimer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnStartTimer.Click
        ''重新開始時間計數
        Me._testTimer.Start()
    End Sub
    Private Sub txtCmd_TextChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles txtInterval.TextChanged
        ''重設時間常數
        Me._timerInterval = CInt(txtInterval.Text)
    End Sub

    Private Sub Form3_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        txtInterval.Text = Me._timerInterval.ToString

        Me._testTimer = New Timers.Timer
        Me._testTimer.Interval = Me._timerInterval * 1000
        AddHandler _testTimer.Elapsed, AddressOf Me.TimerOnElapsed
        Me._testTimer.Start()
        txtLog.Text = "Time is Start"
    End Sub

#Region "Handle Timer Event"
    Private Sub TimerOnElapsed(ByVal sender As Object, ByVal e As Timers.ElapsedEventArgs)
        Try
            Me._testTimer.Stop()
            _timerCount = _timerCount + 1
            ''錯誤發生在下面那行
            txtLog.AppendText(Me._timerCount.ToString & vbCrLf) 
        Catch ex As Exception
            MsgBox(ex.ToString)
        Finally
            Me._testTimer.Interval = Me._timerInterval * 1000
            Me._testTimer.Start()
        End Try
    End Sub
#End Region
End Class

因為我要 Handle Timer 的 Elapsed 事件,
所以將事件另外 Handle
    AddHandler _testTimer.Elapsed, AddressOf Me.TimerOnElapsed

但是要把計數值寫入 TextBox 中會出現"跨執行緒作業無效"的錯誤訊息,
所以多加入委派的 Sub 將寫入 Textbox 的事件重新導向
Public Class Form3
    Dim _testTimer As Timers.Timer
    Dim _timerInterval As Integer = 3
    Dim _timerCount As Integer

    ''委派加入 TextBox 字串的動作
    Private Delegate Sub AppendTextBoxCallback(ByVal txtBox As TextBox, ByVal strValue As String)

    Private Sub AppendTextBox(ByVal txtBox As TextBox, ByVal strValue As String)
        If Me.InvokeRequired Then
            Me.Invoke(New AppendTextBoxCallback(AddressOf Me.AppendTextBox), New Object() {txtBox, strValue})
        Else
            txtBox.AppendText(vbCrLf & strValue)
        End If
    End Sub

    Private Sub btnStopTimer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnStopTimer.Click
        ''停止時間
        Me._testTimer.Stop()
    End Sub
    Private Sub btnStartTimer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnStartTimer.Click
        ''重新開始時間計數
        Me._testTimer.Start()
    End Sub
    Private Sub txtCmd_TextChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles txtInterval.TextChanged
        ''重設時間常數
        Me._timerInterval = CInt(txtInterval.Text)
    End Sub

    Private Sub Form3_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        txtInterval.Text = Me._timerInterval.ToString

        Me._testTimer = New Timers.Timer
        Me._testTimer.Interval = Me._timerInterval * 1000
        AddHandler _testTimer.Elapsed, AddressOf Me.TimerOnElapsed
        Me._testTimer.Start()
        txtLog.Text = "Time is Start"
    End Sub

#Region "Handle Timer Event"
    Private Sub TimerOnElapsed(ByVal sender As Object, ByVal e As Timers.ElapsedEventArgs)
        Try
            Me._testTimer.Stop()
            _timerCount = _timerCount + 1
            'txtLog.AppendText(Me._timerCount.ToString & vbCrLf)
            '交由委派的 Sub 去工作
            AppendTextBox(txtLog, Me._timerCount.ToString)
        Catch ex As Exception
            MsgBox(ex.ToString)
        Finally
            Me._testTimer.Interval = Me._timerInterval * 1000
            Me._testTimer.Start()
        End Try
    End Sub
#End Region
End Class

講了那麼多,主要就是要加入 Delegate 這行,
    Delegate Sub AppendTextBoxCallback(ByVal txtBox As TextBox, ByVal strValue As String)

並在要做的動作寫在委派的 Sub 中就可以了。
    Private Sub AppendTextBox(ByVal txtBox As TextBox, ByVal strValue As String)
        If Me.InvokeRequired Then
            Me.Invoke(New AppendTextBoxCallback(AddressOf Me.AppendTextBox), New Object() {txtBox, strValue})
        Else
            ''想要做的事情寫在這裡
        End If
    End Sub

2011年12月13日 星期二

給永遠的思懷姐

去年底,聽到思懷姐得了癌症,
突然覺得腦筋一片空白,
心裡有好多的問號?
為什麼這麼突然,怎麼會這樣...
似乎這個情形都會發生在,初聽聞家人,得到不治之症時的震驚表現吧!

認識思懷姐是在我高中時期,當時她跑來約我和弟弟一起去參加喜樂營,
當時的我正值叛逆期,凡事總以自我為中心,
感覺自己很行,什麼事都能做,
翹課打架是家常便飯,
甚至積滿三大過被退學。
換了間學校後,還是沒有多大長進,崇拜著進出少年看守所的同學或朋友,
好像他們才是大哥一樣!

這時的我對什麼喜樂營跟本完全不感興趣,還有點排斥,
父母也認為我是個沒救的小孩,我總覺得他們想要把我丟給思懷姐,看他能不能帶我?
最後在母親的勸說和弟弟的拜託下,心不干情不願的答應去什麼鳥喜樂營。

當期的喜樂營是到南投縣雙龍村的一個小部落,主要是學習如何帶領小朋友,如何認識基督教信仰。
到當地,看到原汁原味的部落,迎面而來的是天真無邪的布農族小朋友,
看到我們第一句話就問"你們是歪國人嗎?"
當時確實有被這一幕嚇到,想不到在台灣的這個土地上,
竟然有這樣的朋友,沒有見過"一般人"
所以在那裡的兩個星期,我開始學習如何帶領小孩子,如何上台講故事,如何讓基督教信仰更加堅定。
也學會了反省自己的渺小,和人與人的相處之道。
思懷姐在這其中也幫了我不少忙,讓我成長了很多。

在後來的日子裡,大家各奔東西,
思懷姐朝著傳福音的方向走,
我則努力的彌補荒唐的年輕歲月,
這其間,我會打聽思懷姐的情況,
也曾去到嘉義找過她,我總是對她有著幾分的敬意,
她就像我的大姐姐一樣,把我從歪斜的人生拉了回來,改變了我的一生。

恩懷姐在化療的其間,我一直不敢去看她,
我怕外面的病菌會傳染給她,更怕我的眼淚不爭氣的流下來,
在家裡只能一直問著老媽,思懷姐的情形如何....
除了禱告,還是只能不停的禱告。

最後,我提起勇氣去看了她,
還沒開口,我的眼淚就快流出來了,果然被我家大人說中了,
原來我是愛哭鬼?
第一次去看思懷姐,她氣色還不錯,可以講話,聊天,
要我和弟弟講笑話給他聽,
那時都要哭出來了,也想不出什麼笑話可以講....
思懷姐那天說了一句話,
我很努力的活著,為什麼有人這麼輕易就去跳樓呢?
是呀,有勇氣跳樓,卻沒有勇氣活著?
偏偏就是有這種人.....

第二次去看思懷姐時,
因為鼻子過敏,不停的流鼻涕,我想又要被誤會是愛哭鬼了吧!
其實第二次去看思懷姐時,因為化療的關係,她把頭髮剪掉了,
看著思懷姐消瘦的臉頰,和腫脹的雙腿,真的十分不捨。
這時的她無法講太多話,我也不敢留太久,只想要思懷姐好好休息。

2011/11/19
思懷姐突然說想要去見耶穌了,
聽到這個消息心裡真是難過又覺得高興,
不捨思懷姐離開我們,但又欣慰思懷姐不用再受苦了

2011/11/20
思懷姐的家人準備好一些事情,等著思懷姐到主的懷抱裡,
晚上我和弟弟也趕到醫院要陪思懷姐在人間的最後一程,
醫護人員說,人在臨終時,心跳會到160左右,
聽說這時人算是靈魂與肉體接近分離的時候
然後心跳會到 110 左右,起起伏伏...直到停止
當天思懷姐就這樣靈魂離開又回來,然後離開了又回來......
看到了耶穌,見到了天堂
感覺思懷姐這樣跑來跑去應該很累吧~~~
我們在醫院裡,一一的和思懷姐道別,
我握著思懷姐的手,發現他的手變得好細小,想不到一個人可以瘦成這個樣子,
腦筋裡有好多的話想對思懷姐說,可是我又說不出來,
總是卡在喉嚨裡,強忍著眼淚,勉強擠出幾個字,要思懷姐安心的走...
後來思懷姐的爸爸(盧牧師)來到醫院,看著思懷姐,摸著她的頭,
輕聲說著:[哇A寶貝查某子,你看起來金水,你就安心的去吧!]

我的腦海裡突然閃過一個畫面,
王永在先生,看到王永慶的遺體時,喊了一聲[阿兄!]
這是一個手足之間,深厚的感情,全都濃縮在這兩個字裡。
那是痛徹心扉,發自內心的哭喊。

盧牧師的這幾句話,道出了一個父親對女兒的不捨,
他完全的順服,放手讓自己的女兒到天父身邊。
此時完全無法用任何的言語去安慰和表達,
因為我們都能感受到那份愛是多麼的強烈。

大家在那裡也一起唱詩歌,希望能讓思懷姐平靜的走...
就在我們道別完後不久,思懷姐突然醒了,精神很好,
可以和我們聊天講話,還會笑,
我們還和思懷姐合照,講笑話,
盧牧師和牧師娘在旁邊刮痧,
慈敏幫思懷姐抹油,
整個場面完全感受不到這是一個臨終的人該出現的情景。
後來因為時間不早了,和思懷姐道別,實在搞不懂神的旨意。

2011/11/22
思懷姐明白神的心意了(以下內容摘自 Espenanza Lu 的 FB )
-----------------------------------------------------------
早上醒來不再問為什麼還在醫院,懷說:我明白父神的心意了。
她走過天堂,看見聖城、耶路撒冷,漂亮景物,說耶穌很高很帥。

原來,神在破碎她,也藉著與眾人的合好,釋放靈裡的自由,神也藉此修復我們家人的關係,道歉、道謝是多麼的重要。
----------------------------------------------------------
哦~真是太奇妙了,思懷姐竟然這樣活過來了!
還可以用台語傳福音,
大家聽到這個消息真的覺得很不可思異,也很高興,
原來父神是要思懷姐再次傳福音給其他人。

自從思懷姐出院後因為一個多月沒有進食,都只靠著打針,
胃多有受影響,出院後只吃一點點食物,
不過腳的腫脹開始消腫了,
腹部的積水也消了,
連止痛貼片(嗎啡)都沒使用了...
感覺好像思懷姐可以再次我們一同飲食,一同歡笑。

2011/12/12
今天突然收到亞男傳來的簡訊,
思懷姐於10:15分安息了,走的很平靜。
前一天晚上才和爸媽聊到思懷姐的情況,
老媽還想說等思懷姐好一點再一起去看她,
怎麼會這麼突然?
或許天父要思懷姐做的工已經完成了,
所以在睡夢中將思懷姐接回天家,
那美好的仗,他已經打過了。

這是思懷姐去年聖誕節(2010)住院~晚上跟醫院請假出來主持聖誕晚會


以此文獻給我最敬愛的思懷姐!
願思懷姐在天堂得著快樂與平安,總有一天我們會在天家相見的。