使用.NET多線程技術(shù)顯示實時股票信息

2010-08-28 10:50:01來源:西部e網(wǎng)作者:

    本文的內(nèi)容是學習如何使用多線程技術(shù)建立應用程序,使應用程序在執(zhí)行時間和資源密集型后臺事務的時候,用戶界面(UI)仍然保持活動狀態(tài)。

  多線程技術(shù)(multithreading)是編程中最強大的概念之一。使用多線程技術(shù),你可以把復雜的事務拆分到彼此獨立執(zhí)行的多個線程之中。良好的多線程應用程序是自然地同步的,類似于Web服務調(diào)用。在默認情況下,Web服務調(diào)用屬于阻塞(blocking)調(diào)用--即調(diào)用者(caller)的代碼停止執(zhí)行,直到Web服務返回結(jié)果為止。但是由于Web服務調(diào)用通常很慢,就可能導致客戶端性能降低,除非你采用特殊的步驟使調(diào)用異步進行。

  本文講解的是如何建立一個圖表應用程序,從這個例子中你可以看到如何在不影響客戶端UI的時候異步地調(diào)用Web服務。示例代碼利用Chart FX組件使用圖形來顯示股票信息。當然讀者也可以使用.NET編寫的免費圖表類庫。

  建立一個Web服務

  示例代碼需要訪問假想的股票報價Web服務。我們在Visual Studio .NET 2003中建立一個Web服務,把它命名為"StockWS"。這個Web服務由一個叫做getPrice()的Web方法組成,該方法只接受一個股票編碼參數(shù):

Public Function getPrice(ByVal stock As String) As Single
 Return Rnd() * 100
End Function

  不管被請求的股票是什么,getPrice()方法都生成一個隨機的價格。它的唯一目標是模擬一個返回特定股票價格的真實的Web服務。
盡管本文使用的是一個成型的Web服務來進行演示的,但是你可以輕易地替換這個Web服務以顯示真正的股票信息。

  使用Chart FX組件顯示圖形

  在建立上面的Web服務項目之后,先給解決方案瀏覽器添加一個Windows應用程序項目(叫做Stock Quote,股票報價)。給該項目增加一個對前面所建立的Web服務的引用。解決方案瀏覽器現(xiàn)在應該如圖1所示。


圖1:解決方案瀏覽器中的項目-圖中顯示了StockWS Web服務項目和

  Windows窗體項目Stock Quote

  為了建立本文的示例項目,你必須從http://chartfx.com/下載和安裝Chart FX組件30天試用版。在安裝這個繪圖組件之后,你可以在Visual Studio .NET 2003的工具盒中看到它(如圖2所示)。


圖2:工具盒中的Chart組件-你需要從網(wǎng)站上下載并安裝Chart FX組件30天試用版。

  在該Windows應用程序默認的Form1中,用下面一些控件填充該窗體,如圖3所示:

  · Chart
  · ComboBox
  · Button

點擊放大此圖片
圖3:Stock Quote主窗體-圖中顯示了添加適當?shù)目丶蟠绑w樣式。

  Chart(繪圖)組件為定制自己的行為和外觀提供了很多選項。你可以使用向?qū)Вㄎ挥趯傩源绑w底部,如圖4所示)格式化這個Chart組件。


圖4:Chart組件的格式化向?qū)В撓驅(qū)镃hart組件提供了大量的格式化選項。

  使用示例的最簡單的方法是把下面一些Chart屬性復制并粘貼到"Windows窗體設計器生成的"代碼段中:

'Chart1
Me.Chart1.AxisX.Staggered = True
Me.Chart1.AxisX.Step = 10
Me.Chart1.AxisY.Step = 10
Me.Chart1.BackObject = GradientBackground1
Me.Chart1.DataStyle =SoftwareFX.ChartFX.DataStyle.ReadXValues
Me.Chart1.DesignTimeData = _
"C:\Program Files\ChartFX for .NET 6.2\Wizard\XYZero.txt"
Me.Chart1.Gallery = SoftwareFX.ChartFX.Gallery.Lines
Me.Chart1.InsideColor = System.Drawing.Color.Transparent
Me.Chart1.LineWidth = 3
Me.Chart1.Location = New System.Drawing.Point(40, 16)
Me.Chart1.MarkerShape =SoftwareFX.ChartFX.MarkerShape.None
Me.Chart1.Name = "Chart1"
Me.Chart1.NSeries = 1
Me.Chart1.NValues = 20
Me.Chart1.Palette = "HighContrast.HighContrast"
Me.Chart1.PointLabels = True
Me.Chart1.Size = New System.Drawing.Size(656, 216)
Me.Chart1.TabIndex = 12
Me.Chart1.Titles.AddRange(New _
SoftwareFX.ChartFX.TitleDockable(){TitleDockable1})

  同時,把下面一些數(shù)據(jù)項添加到組合框控件中:"MSFT"、"SUN"、"YHOO"和"GE"。你可以在Form_Load事件中進行這樣的操作:

Me.cmbStocks1.Items.AddRange(New String() {"MSFT", "SUN", "YHOO", "GE"})

 

激活圖形

  下一步,導入下面的名字空間(在代碼窗口的頂部):

Imports SoftwareFX.ChartFX
Imports System.Threading


  定義用于線程的全局變量t1:

Dim t1 As Thread


  在Chart1_Load事件中,初始化Chart組件:

Private Sub Chart1_Load(ByVal sender As _
System.Object, ByVal e As System.EventArgs) Handles Chart1.Load
 'x軸上每隔5點顯示時間
 Chart1.AxisX.Step = 5
 '每個點之間用5象素間隔
 Chart1.AxisX.PixPerUnit = 5
 '使圖表可以滾動
 Chart1.Scrollable = True
 '打開和關(guān)閉通訊管道
 Chart1.OpenData(COD.Values, 1, COD.Unknown)
 Chart1.CloseData(COD.Values)
End Sub


  給當前的窗體添加一個叫做StockQuote的類。StockQuote類調(diào)用前面的Web服務并用返回的股票價格來更新圖表。

Public Class StockQuote
 '組件中圖形的數(shù)量
 Const NUM_SERIES = 1

 Private lastPoint As Integer = 0
 Dim stockPrice As Single

 Private pStockSymbol As String
 Private pStockSeries As Integer = 0
 Private pChartControl As Chart

 WriteOnly Property StockSymbol()
  Set(ByVal Value)
   pStockSymbol = Value
  End Set
 End Property

 WriteOnly Property ChartControl()
  Set(ByVal Value)
   pChartControl = Value
  End Set
 End Property

 Public Sub InvokeWebService()
  Dim ws As New StockWS.Service1

  For i As Integer = 0 To 10000
   stockPrice = ws.getPrice(pStockSymbol)
   pChartControl.Invoke(New _
myDelegate(AddressOf updateChart), New Object() {})
   '繼續(xù)之前等待1秒鐘
   Thread.Sleep(1000)
  Next
 End Sub

 Public Delegate Sub myDelegate()
  Public Sub updateChart()
  pChartControl.OpenData(COD.Values, NUM_SERIES, COD.Unknown)
  pChartControl.Value(pStockSeries, lastPoint) = stockPrice
  '顯示x軸上的時間
  pChartControl.AxisX.Label(lastPoint) = DateTime.Now.ToShortTimeString
  lastPoint += 1
  pChartControl.CloseData(COD.Values)
  '把滾動條移到最右邊
  pChartControl.AxisX.ScrollPosition = pChartControl.AxisX.ScrollSize
 End Sub

End Class


  你通過StockSymbol屬性把需要的股票編碼傳遞給StockQuote類,并使用ChartControl屬性設置圖表更新。InvokeWebService()方法在循環(huán)(示例中設置為10,000)中周期性地調(diào)用上面的Web服務。由于這個類會在一個單獨的線程中執(zhí)行,你必須非常小心以確保自己不會自動地更新某個Windows控件,因為Windows控件并不是線程安全的(thread-safe)。作為代替,你必須使用委托并調(diào)用自己希望更新的控件上的Invoke()方法。代碼每秒鐘調(diào)用Web服務一次,這是由Thread.Sleep(1000)語句設置的。

  為了啟動線程用最新的股票信息更新圖表,給"獲取股票報價"按鈕的點擊(Click)事件增加下面的代碼:

Private Sub btnGetStockQuote1_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnGetStockQuote1.Click
 Dim sq As New StockQuote
 sq.StockSymbol = cmbStocks1.SelectedItem
 sq.ChartControl = Chart1
 t1 = New Thread(AddressOf sq.InvokeWebService)
 t1.Start()
End Sub


  把調(diào)用該Web服務的代碼打包為一個類的主要原因是Thread類構(gòu)造函數(shù)只能接受一個ThreadStart委托(啟動線程的方法的委托),不存在可以接受多個參數(shù)值的重載的Thread.Start()方法。因此,把多個參數(shù)傳遞到一個線程中的唯一途徑是把調(diào)用的相關(guān)代碼打包為一個類,接著你就可以通過這個類的參數(shù)來傳遞參數(shù)。

  按F5測試這段代碼,選擇一只股票并點擊"獲取股票報價"按鈕。你現(xiàn)在可以移動窗口了(即UI并沒有被重復的Web服務調(diào)用鎖死),并且同時可以看到圖表一直在用最新的股票信息更新(圖5所示)。

點擊放大此圖片
圖5:測試該應用程序-當你選擇某只股票編碼并點擊"獲取股票報價"按鈕的時候,重復調(diào)用Web服務的結(jié)果顯示在圖表中;但是由于該Web服務運行在后臺線程上,調(diào)用它不會影響正常的UI操作。
 

顯示多只股票的價格

  你已經(jīng)看到了如何在保證應用程序的UI不停頓的情況下異步地調(diào)用Web服務了;但是,你還可以增強該應用程序來同時顯示多個信息。

  在同一個窗體中,增加另一組控件(ChartFX、組合框和按鈕)和標簽、暫停、停止按鈕(如圖6所示)。

點擊放大此圖片
圖6:增強的多股票窗體-此圖顯示了你需要添加到默認窗體上以同時顯示兩只股票圖形的新控件。

  這個增強的示例同時顯示了兩個圖形,還要顯示用于顯示第二個圖形的線程的狀態(tài)信息。

  添加第二個全局變量t2:

Dim t1, t2 As Thread

  示例項目使用計時器控件(Timer,在工具箱中)來顯示第二個線程的狀態(tài)信息。把計時器拖放到窗體上,并把它的Interval屬性設置為500,這使該計時器的Tick事件每半秒鐘(500毫秒)調(diào)用一次。Tick事件處理程序中的代碼更新了標簽控件lblThreadStatus中的線程狀態(tài)信息:

Private Sub Timer1_Tick(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Timer1.Tick
 lblThreadStatus.Text = "Thread state: " & _
t2.ThreadState.ToString
End Sub

  第二個圖表也使用與第一個圖表相同的初始化代碼:

Private Sub Chart2_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Chart2.Load
 '在x軸上每5點顯示時間
 Chart2.AxisX.Step = 5
 '每個點之間用5個象素分隔
 Chart2.AxisX.PixPerUnit = 5
 '使圖表可以滾動
 Chart2.Scrollable = True
 '打開和關(guān)閉通訊管道-
 Chart2.OpenData(COD.Values, 1, COD.Unknown)
 Chart2.CloseData(COD.Values)
End Sub

  你點擊第二個圖表的"獲取股票報價"按鈕的時候,代碼建立一個新的線程--同時激活計時器,這樣窗體才能夠顯示線程的狀態(tài)信息:

Private Sub btnGetStockQuote2_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnGetStockQuote2.Click
 Dim sq As New StockQuote
 sq.StockSymbol = cmbStocks2.SelectedItem
 sq.ChartControl = Chart2
 t2 = New Thread(AddressOf sq.InvokeWebService)
 t2.Start()

 '激活暫停和停止按鈕
 btnPauseContinue.Enabled = True
 btnStop.Enabled = True
 '激活計時器控件
 Timer1.Enabled = True
End Sub

  按F5測試這兩個圖表(圖7所示)。為每個圖表選擇一只股票,你將看到這兩個圖表同步顯示。

點擊放大此圖片
圖7:增強的兩圖表應用程序-增強的版本同時顯示了兩個圖表。

  當?shù)诙䝼線程運行的時候,你可以注意到其狀態(tài)在Running和WaitSleepJoin之間交替。這是因為某個線程要么在執(zhí)行(Running),要么在睡眠(WaitSleepJoin)。當該線程被暫停的時候,它的狀態(tài)是WaitSleepJoin、Suspended。當該線程被取消的時候,它的狀態(tài)先是AbortRequested,接著變成了Stopped。

  如果要暫停該線程,需要首先檢測運行中線程的狀態(tài),然后使用Suspend()方法。在暫停一個線程之后,你可以使用Resume()方法繼續(xù)執(zhí)行它。

Private Sub btnPauseContinue_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnPauseContinue.Click
' 如果線程處于睡眠和運行狀態(tài)就掛起它
If t2.ThreadState = ThreadState.WaitSleepJoin _
 Or t2.ThreadState = ThreadState.Running Then
 t2.Suspend()
 btnPauseContinue.Text = "Continue"
Else
 ' 繼續(xù)該線程
 t2.Resume()
 btnPauseContinue.Text = "Pause"
End If
End Sub

  停止線程則使用Abort()方法:

Private Sub btnStop_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnStop.Click
Try
 If Not t2.ThreadState = ThreadState.Stopped Then
  btnPauseContinue.Enabled = False
  btnStop.Enabled = False
  t2.Abort()
 End If
Catch ex As Exception
 MsgBox(ex.ToString)
End Try
End Sub

  通過運行示例項目,你會發(fā)現(xiàn)自己已經(jīng)能夠使用多線程技術(shù)建立應用程序,使應用程序在執(zhí)行后臺事務的時候,仍然保持響應。盡管本文的示例使用的是Web服務,但是相同的原則也可以應用于其它類型的后臺事務。例如,你可以改變這個應用程序以讀取外部設備(例如溫度計或血壓計監(jiān)視設備)的數(shù)據(jù)。
關(guān)鍵詞:.NET