實質上鼓勵一下吧

多執行緒的應用實在很多,以證券公司出每天每一個客戶的交易記錄報表為例,早上九點到下午兩點是交易時間,必須把所有的資料庫資源留給大量的交易做為儲存用。而交易時間過後就是證券公司的結帳時間,雖然這段時間有很多人工作業,但是資料並非完全正確,所以也不能拿來利用,所以只有在結帳時間結束,才能夠把資料拿來進行報表的輸出作業。也就是說大約在三點半之後才能夠把資料進行報表輸出處理。

因為每一張報表都是個人化的,如果當天有一萬個客戶進行交易(這對證券公司來說算是很少的),就算說每一張報表只需要0.5秒的時間,如果使用傳統的循序處理方法,那麼整個輸出過程就需要1.4個小時才能完成。現在的電腦CPU都是超多的,而循序方法卻都只用到一顆CPU的功能,就以普通級的伺服器來說,四顆CPU再乘以四核心,等於有16顆CPU可以使用,如果能夠充分利用CPU的能力,就算一顆一次只能處理一個進程,那麼也只需要5分鐘就能完成所有工作,何況一顆CPU同時不止能夠處理一個進程,不使用多執行緒不但對不起自己也對不起客戶。

這個例子該如何設計也是一門學問,因為是大量資料讀取的處理,而且資料基本上已經固定不會修改,如果伺服器的記憶體夠大,我會建議一次把所有用得到的資料讀進記憶體中,這樣會使得資料庫的負擔只有在讀取的一段時間,而不是在大量執行緒同時進行的時刻,當然這要看資料庫伺服器的處理能力和資料量而定。

Dim strConn As String = "…"
Dim csvConn As New OdbcConnection(strConn)
Dim strSql As String = "select * from …"
Dim daDat As New OdbcDataAdapter(strSql, csvConn)
Dim dtDat As New DataTable
daDat.Fill(dtDat)

上述就是一種最簡單的方法,一次把所有的資料都讀進記憶體內,然後再使用DataView的過濾功能過濾資料,這樣可以減輕資料庫進行多次讀取的負擔,但是處理報表的伺服器就必須要有很大的記憶體。

Dim dvDat As New DataView(dtDat)
dvDat.Filter = "CustomNo=…."

有了資料庫處理方法,再來就是如何進行多執行緒的設計。基本的多執行緒處理請參考前面的用VB.NET實現多執行緒,接下來要說明的是設計上的細節。

'要傳進Thread的參數
Public Class ReportArg
  Public CustomerNo As String
  '宣告一個初始化函數簡化參數宣告時的程式碼
  Public Sub New(ByVal CustomerNo As String)
    Me.CusomerNo = CustomerNo
  End Sub
End Class
 
因為將程序丟進執行緒駐列的時候只能夠傳入Object必須先宣告一個參數物件。如果參數不多可以宣告一個物件初始化程序,讓參數一次都能夠傳進去,這樣使用的程式碼看起來比較簡潔。
 
Public Class CustomReport
  Public Event ThreadMonitor(ByVal JobDone As Integer)
  Private JobDone As Integer
 
  Public Sub GenReport()
    For Each drvDat As DataRowView In dvDat
      '產生一個執行緒物件,準備丟進執行緒駐列
callBack = New WaitCallback(AddressOf GenReport2)
      '建立一個參數物件
       Dim Arg As New ReportArg(drvDat("CustomNo"))
      '將執行緒物件丟進執行緒駐列,並且帶上參數
ThreadPool.QueueUserWorkItem(callBack, Arg)
      '注解一條程式碼,用來進行單執行緒測試用
       'GenReport(Arg)
JobTotal += 1
Next
    '等待所有的執行緒完成
While (JobDone < JobTotal)
      '觸發一個事件,可以讓主程式視窗看到執行的進度
      RaiseEvent ThreadMonitor(JobDone)
      '睡個0.5秒再看看,如果不是那麼迫切要看進度,這個數值可以加大
Thread.Sleep(500)
End While
  End Sub
 
  Public Sub GenReport2(ByVal Arg As Object)
    Dim dvDat As New DataView(dtDat)
dvDat.Filter = "CustomNo=…."
    '輸出報表的程式……
    '鎖定自己這個物件,因為要使用物件內變數
    SyncLock Me
      JobDone += 1
    End SyncLock
  End Sub
End Class
 

這一段就是呼叫多執行緒的主程式,基本上跟前面上一篇所提到的程式碼差不多。增加了一個注解程式碼,因為一旦做了多執行緒,幾乎就沒有辦法除錯,所以用一個原始的程式碼來充當除錯用的工具,這樣就能夠一行一行的追蹤程式。


並且在這個類別增加一個事件,讓呼叫它的視窗程式可以看到執行的進度,當然你可以依照你的需求增加傳出去的參數。

Dim WithEvents Report As New CustomReport
 
Private Sub Button1_Click(……) Handles Button1.Click
    Report.GenReport()
End Sub
 
Private Sub Report_ThreadMonitor(ByVal JobDone As Integer) _
  Handles Report.ThreadMonitor
  labelGenCount.Text = String.Format("Count: {0}", JobDone)
  '讓CPU空出來刷新畫面
  Application.DoEvents()
End Sub
 

呼叫多執行緒的主程式大概的樣子如上,要注意的是監控程式要越簡單越好,不然整個程式會被它給拖慢下來。


PS.有程式碼的文章版面就變得亂七八糟,誰能幫幫我呀!

創作者介紹

人生四十宅開始 二號宅

漠哥 發表在 痞客邦 留言(2) 人氣()


留言列表 (2)

發表留言
  • DCP
  • 可以用
    ---------------------------------
    ^^^^^^^^^^^^^^^^^^^^這個分割或許就比較不會亂
    或 < hr >