多線程技術(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
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所示)。
這個增強的示例同時顯示了兩個圖形,還要顯示用于顯示第二個圖形的線程的狀態(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所示)。為每個圖表選擇一只股票,你將看到這兩個圖表同步顯示。
當?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ù)。