本文的内容是学习如何使用多线程技术建立应用程序
使应用程序在执行时间和资源密集型后台事务的时候
用户界面(UI)仍然保持活动状态
多线程技术(multithreading)是编程中最强大的概念之一使用多线程技术你可以把复杂的事务拆分到彼此独立执行的多个线程之中良好的多线程应用程序是自然地同步的类似于Web服务调用在默认情况下Web服务调用属于阻塞(blocking)调用即调用者(caller)的代码停止执行直到Web服务返回结果为止但是由于Web服务调用通常很慢就可能导致客户端性能降低除非你采用特殊的步骤使调用异步进行
本文讲解的是如何建立一个图表应用程序从这个例子中你可以看到如何在不影响客户端UI的时候异步地调用Web服务示例代码利用Chart FX组件使用图形来显示股票信息当然读者也可以使用NET编写的免费图表类库
建立一个Web服务
示例代码需要访问假想的股票报价Web服务我们在Visual Studio NET 中建立一个Web服务把它命名为StockWS这个Web服务由一个叫做getPrice()的Web方法组成该方法只接受一个股票编码参数
Public Function getPrice(ByVal stock As String) As Single
Return Rnd() *
End Function
不管被请求的股票是什么getPrice()方法都生成一个随机的价格它的唯一目标是模拟一个返回特定股票价格的真实的Web服务
尽管本文使用的是一个成型的Web服务来进行演示的但是你可以轻易地替换这个Web服务以显示真正的股票信息
使用Chart FX组件显示图形
在建立上面的Web服务项目之后先给解决方案浏览器添加一个Windows应用程序项目(叫做Stock Quote股票报价)给该项目增加一个对前面所建立的Web服务的引用解决方案浏览器现在应该如图所示
图解决方案浏览器中的项目-图中显示了StockWS Web服务项目和
Windows窗体项目Stock Quote
为了建立本文的示例项目你必须从下载和安装Chart FX组件天试用版在安装这个绘图组件之后你可以在Visual Studio NET 的工具盒中看到它(如图所示)
图工具盒中的Chart组件你需要从网站上下载并安装Chart FX组件天试用版
在该Windows应用程序默认的Form中用下面一些控件填充该窗体如图所示
· Chart
· ComboBox
· Button
图Stock Quote主窗体-图中显示了添加适当的控件之后窗体样式
Chart(绘图)组件为定制自己的行为和外观提供了很多选项你可以使用向导(位于属性窗体底部如图所示)格式化这个Chart组件
图Chart组件的格式化向导-该向导为Chart组件提供了大量的格式化选项
使用示例的最简单的方法是把下面一些Chart属性复制并粘贴到Windows窗体设计器生成的代码段中
Chart
MeChartAxisXStaggered = True
MeChartAxisXStep =
MeChartAxisYStep =
MeChartBackObject = GradientBackground
MeChartDataStyle =SoftwareFXChartFXDataStyleReadXValues
MeChartDesignTimeData = _
C:\Program Files\ChartFX for NET \Wizard\XYZerotxt
MeChartGallery = SoftwareFXChartFXGalleryLines
MeChartInsideColor = SystemDrawingColorTransparent
MeChartLineWidth =
MeChartLocation = New SystemDrawingPoint( )
MeChartMarkerShape =SoftwareFXChartFXMarkerShapeNone
MeChartName = Chart
MeChartNSeries =
MeChartNValues =
MeChartPalette = HighContrastHighContrast
MeChartPointLabels = True
MeChartSize = New SystemDrawingSize( )
MeChartTabIndex =
MeChartTitlesAddRange(New _
SoftwareFXChartFXTitleDockable(){TitleDockable})
同时把下面一些数据项添加到组合框控件中MSFTSUNYHOO和GE你可以在Form_Load事件中进行这样的操作
MecmbStocksItemsAddRange(New String() {MSFT SUN YHOO GE}) 激活图形
下一步导入下面的名字空间(在代码窗口的顶部)
Imports SoftwareFXChartFX
Imports SystemThreading
定义用于线程的全局变量t:
Dim t As Thread
在Chart_Load事件中初始化Chart组件
Private Sub Chart_Load(ByVal sender As _
SystemObject ByVal e As SystemEventArgs) Handles ChartLoad
x轴上每隔点显示时间
ChartAxisXStep =
每个点之间用象素间隔
ChartAxisXPixPerUnit =
使图表可以滚动
ChartScrollable = True
打开和关闭通讯管道
ChartOpenData(CODValues CODUnknown)
ChartCloseData(CODValues)
End Sub
给当前的窗体添加一个叫做StockQuote的类StockQuote类调用前面的Web服务并用返回的股票价格来更新图表
Public Class StockQuote
组件中图形的数量
Const NUM_SERIES =
Private lastPoint As Integer =
Dim stockPrice As Single
Private pStockSymbol As String
Private pStockSeries As Integer =
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 StockWSService
For i As Integer = To
stockPrice = wsgetPrice(pStockSymbol)
pChartControlInvoke(New _
myDelegate(AddressOf updateChart) New Object() {})
继续之前等待秒钟
ThreadSleep()
Next
End Sub
Public Delegate Sub myDelegate()
Public Sub updateChart()
pChartControlOpenData(CODValues NUM_SERIES CODUnknown)
pChartControlValue(pStockSeries lastPoint) = stockPrice
显示x轴上的时间
pChartControlAxisXLabel(lastPoint) = DateTimeNowToShortTimeString
lastPoint +=
pChartControlCloseData(CODValues)
把滚动条移到最右边
pChartControlAxisXScrollPosition = pChartControlAxisXScrollSize
End Sub
End Class
你通过StockSymbol属性把需要的股票编码传递给StockQuote类并使用ChartControl属性设置图表更新InvokeWebService()方法在循环(示例中设置为)中周期性地调用上面的Web服务由于这个类会在一个单独的线程中执行你必须非常小心以确保自己不会自动地更新某个Windows控件因为Windows控件并不是线程安全的(threadsafe)作为代替你必须使用委托并调用自己希望更新的控件上的Invoke()方法代码每秒钟调用Web服务一次这是由ThreadSleep()语句设置的
为了启动线程用最新的股票信息更新图表给获取股票报价按钮的点击(Click)事件增加下面的代码
Private Sub btnGetStockQuote
_Click(ByVal sender As System
Object
_
ByVal e As SystemEventArgs) Handles btnGetStockQuoteClick
Dim sq As New StockQuote
sqStockSymbol = cmbStocksSelectedItem
sqChartControl = Chart
t = New Thread(AddressOf sqInvokeWebService)
tStart()
End Sub
把调用该Web服务的代码打包为一个类的主要原因是Thread类构造函数只能接受一个ThreadStart委托(启动线程的方法的委托)不存在可以接受多个参数值的重载的ThreadStart()方法因此把多个参数传递到一个线程中的唯一途径是把调用的相关代码打包为一个类接着你就可以通过这个类的参数来传递参数
按F测试这段代码选择一只股票并点击获取股票报价按钮你现在可以移 动窗口了(即UI并没有被重复的Web服务调用锁死)并且同时可以看到图表一直在用最新的股票信息更新(图所示)
图测试该应用程序-当你选择某只股票编码并点击获取股票报价按钮的时候重复调用Web服务的结果显示在图表中但是由于该Web服务运行在后台线程上调用它不会影响正常的UI操作 显示多只股票的价格你已经看到了如何在保证应用程序的UI不停顿的情况下异步地调用Web服务了但是你还可以增强该应用程序来同时显示多个信息
在同一个窗体中增加另一组控件(ChartFX组合框和按钮)和标签暂停停止按钮(如图所示)
图增强的多股票窗体-此图显示了你需要添加到默认窗体上以同时显示两只股票图形的新控件
这个增强的示例同时显示了两个图形还要显示用于显示第二个图形的线程的状态信息
添加第二个全局变量t
Dim t t As Thread
示例项目使用计时器控件(Timer在工具箱中)来显示第二个线程的状态信息把计时器拖放到窗体上并把它的Interval属性设置为这使该计时器的Tick事件每半秒钟(毫秒)调用一次Tick事件处理程序中的代码更新了标签控件lblThreadStatus中的线程状态信息
Private Sub Timer_Tick(ByVal sender As SystemObject _
ByVal e As SystemEventArgs) Handles TimerTick
lblThreadStatusText = Thread state: & _
tThreadStateToString
End Sub
第二个图表也使用与第一个图表相同的初始化代码
Private Sub Chart_Load(ByVal sender As SystemObject _
ByVal e As SystemEventArgs) Handles ChartLoad
在x轴上每点显示时间
ChartAxisXStep =
每个点之间用个象素分隔
ChartAxisXPixPerUnit =
使图表可以滚动
ChartScrollable = True
打开和关闭通讯管道
ChartOpenData(CODValues CODUnknown)
ChartCloseData(CODValues)
End Sub
你点击第二个图表的获取股票报价按钮的时候代码建立一个新的线程同时激活计时器这样窗体才能够显示线程的状态信息
Private Sub btnGetStockQuote
_Click(ByVal sender As System
Object
_
ByVal e As SystemEventArgs) Handles btnGetStockQuoteClick
Dim sq As New StockQuote
sqStockSymbol = cmbStocksSelectedItem
sqChartControl = Chart
t = New Thread(AddressOf sqInvokeWebService)
tStart()
激活暂停和停止按钮
btnPauseContinueEnabled = True
btnStopEnabled = True
激活计时器控件
TimerEnabled = True
End Sub
按F测试这两个图表(图所示)为每个图表选择一只股票你将看到这两个图表同步显示
图增强的两图表应用程序增强的版本同时显示了两个图表
当第二个线程运行的时候你可以注意到其状态在Running和WaitSleepJoin之间交替这是因为某个线程要么在执行(Running)要么在睡眠(WaitSleepJoin)当该线程被暂停的时候它的状态是WaitSleepJoinSuspended当该线程被取消的时候它的状态先是AbortRequested接着变成了Stopped
如果要暂停该线程需要首先检测运行中线程的状态然后使用Suspend()方法在暂停一个线程之后你可以使用Resume()方法继续执行它
Private Sub btnPauseContinue_Click(ByVal sender As SystemObject _
ByVal e As SystemEventArgs) Handles btnPauseContinueClick
如果线程处于睡眠和运行状态就挂起它
If tThreadState = ThreadStateWaitSleepJoin _
Or tThreadState = ThreadStateRunning Then
tSuspend()
btnPauseContinueText = Continue
Else
继续该线程
tResume()
btnPauseContinueText = Pause
End If
End Sub
停止线程则使用Abort()方法
Private Sub btnStop_Click(ByVal sender As SystemObject _
ByVal e As SystemEventArgs) Handles btnStopClick
Try
If Not tThreadState = ThreadStateStopped Then
btnPauseContinueEnabled = False
btnStopEnabled = False
tAbort()
End If
Catch ex As Exception
MsgBox(exToString)
End Try
End Sub
通过运行示例项目你会发现自己已经能够使用多线程技术建立应用程序使应用程序在执行后台事务的时候仍然保持响应尽管本文的示例使用的是Web服务但是相同的原则也可以应用于其它类型的后台事务例如你可以改变这个应用程序以读取外部设备(例如温度计或血压计监视设备)的数据