回呼的秘密花園《State Object》

.NET提供了許多回呼的方法使得撰寫多執行緒與非同步的程式變的較為簡單,像是ThreadPool.QueueUserWorkItem、Socket與其衍生類別的Beginxxxx、ADO.NET中的SqlCommand.Beginxxxxx等等。而這些方法通常在其多載函式中其中至少會有一個具備了State Object,當我剛開始撰寫這一類程式的時候,一直無法弄清楚這個State Object的作用,所以想要特別用簡單的方式來介紹這個Object的用途。

       .NET提供了許多回呼的方法使得撰寫多執行緒與非同步的程式變的較為簡單,像是ThreadPool.QueueUserWorkItem、Socket與其衍生類別的Beginxxxx、ADO.NET中的SqlCommand.Beginxxxxx等等。而這些方法通常在其多載函式中其中至少會有一個具備了State Object,當我剛開始撰寫這一類程式的時候,一直無法弄清楚這個State Object的作用,所以想要特別用簡單的方式來介紹這個Object的用途。

        State Object如果直譯為中文就是「狀態物件」,不過這名字還真是莫測高深,其實用很白話的說法,這個State Object其實是在聯繫呼叫端與被呼叫端的資訊,如果用單一執行緒的思考而言,它就是用來傳遞參數的。傳遞這個State Object的重要性在於能夠讓被引發的執行緒取得與呼叫的執行緒間正確的資訊﹝包含變數﹞。

        這樣說還是很模糊,那就來個範例看看,以下是一個簡單的程式,畫面上有兩個Button控制項,Button1與Button2;有兩個Multiline屬性為True的TextBox控制項,TextBox1與TextBox2。myPublic變數是為了展示不使用State Object傳遞,而使用全域變數的結果;宣告的 myState類別是為了展示傳遞State Object的自訂類別。           

Imports System.Threading
Public Class Form1
    Public Class myState
        Public myPrivate As Integer
    End Class

   Dim myPublic As Integer

    Delegate Sub SetMsg1Callback(ByVal InputString As String)
    Private Sub DisplayMsg1(ByVal strReceive As String)
        If Me.TextBox1.InvokeRequired Then
            Dim d As New SetMsg1Callback(AddressOf DisplayMsg1)
            Me.Invoke(d, New Object() {strReceive})
        Else
            Me.TextBox1.Text &= strReceive
        End If
    End Sub

    Delegate Sub SetMsg2Callback(ByVal InputString As String)
    Private Sub DisplayMsg2(ByVal strReceive As String)

        If Me.TextBox2.InvokeRequired Then
            Dim d As New SetMsg2Callback(AddressOf DisplayMsg2)
            Me.Invoke(d, New Object() {strReceive})
        Else
            Me.TextBox2.Text &= strReceive
        End If
    End Sub

    Private Sub Form1_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing

    End Sub
    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

    End Sub
    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        TextBox1.Text = ""
        For myPublic = 1 To 5
            ThreadPool.QueueUserWorkItem(AddressOf StateTest1)
        Next

    End Sub
    Private Sub StateTest1(ByVal state As Object)
        ' System.Threading.Thread.Sleep(1)

        Dim myDisplayStr As String
        myDisplayStr = CStr(myPublic)
        DisplayMsg1(Now.ToString("HH:mm:ss.fffffff") & ";變數=" & myDisplayStr & vbCrLf)

        Application.DoEvents()
    End Sub

    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
        TextBox2.Text = ""
        Dim i As Integer
        For i = 1 To 5
            Dim myObj As New myState
            myObj.myPrivate = i
            ThreadPool.QueueUserWorkItem(AddressOf StateTest2, myObj)
        Next
    End Sub
    Private Sub StateTest2(ByVal state As Object)
        ' System.Threading.Thread.Sleep(10)
        Dim myDisplayStr As String
        myDisplayStr = CStr((CType(state, myState).myPrivate))
        DisplayMsg2(Now.ToString("HH:mm:ss.fffffff") & ";變數=" & myDisplayStr & vbCrLf)
        Application.DoEvents()

    End Sub
End Class

         StateObject_1

       我們來看看執行後的結果畫面,每一個次執行緒幾乎都在同一個時間完成,而TextBox2所顯示的結果才是符合我們預期的,TextBox1之所以會產生如此奇怪的結果的原因就是因為當StateTest1執行緒執行到myDisplayStr = CStr(myPublic)這行敘述之時myPublic變數的值可能已經產生變化了,在多執行緒的狀況下,許多資訊往往不會如我們所預期。想試試看的人可以在執行緒中加上一些Thread.Sleep並且將迴圈數放大,試的多按幾下Button1,觀察一下TextBox1的內容是不是千奇百怪。而StateTest2因為獲得了正確的資訊,所以顯示出了正確的結果。

       自訂給State Object所使用的類別可以依照自己的需求所訂,主要是要先辨認出哪些資訊是需要傳遞的就加入到此類別中。不熟悉的同好可以自己寫幾個程式試著玩玩看。

按此下載範例內容:StateOBJTest.rar