第 19 章 编写具有多个线程运行的应用程序

40
1 第 19 第 第第第第第第第第第第第第第第第 对对对对对对对对对对对对对 对对对对对对对对对对对对对对对 对对对对对对对对 ,一。 对对对对 对对对对对对对对对对对对对对对对对对对 对对对对对对对对对对对对对 ,。, 对对对对对对对对对对对对对对对对对对 对对对 对对对对对对对对对对对对对对对对 ,一 对对对对对 对对对对对对对对对对对对对对对对对对对对 对对对对对对对对对对对 。,。 对对对对对对对对对对对对对对对对 对对对 对对对对对对对对对对对对对对对对对对 。, 对对对对对对对.NET Framework 对对对对对对对对对对对对对对对 对对对对对对对对对对对对 对对对对对对对对对对对对 对对对对对对对对对对对对对 ,, 对对对对对对 对对对对 VB.NET 对对对对对对对

Upload: mimis

Post on 19-Jan-2016

303 views

Category:

Documents


0 download

DESCRIPTION

第 19 章 编写具有多个线程运行的应用程序. - PowerPoint PPT Presentation

TRANSCRIPT

1

第 19章 编写具有多个线程运行的应用程序

对于比较复杂的应用程序来说,同时执行多个任务的能力通常是一个关键性的特性。在此之前,我们看到的应用程序均属于同步应用程序。尽管同步应用程序易于开发,但是它们的性能通常比多线程应用程序低,因为一个新的事务必须等待前面的事务完成后才能开始。如果完成某个同步事务的时间比预想的要长,应用程序可能没有响应。而多线程处理可以同时运行多个过程。例如,字处理程序能够在继续操作文档的同时执行拼写检查事务。 .NET Framework提供的线程库能够让开发人员自由创建完全线程化的应用程序,整个程序都可以异步执行,从而允许更高效的设计和更高的交互响应性。本章介绍 VB.NET中的多线程技术。

2

章节内容章节内容19.1 了解线程的基本概念19.2 线程创建与管理19.3 使用多线程组件轻松创建多线程应用程序

3

19.1 19.1 了解线程的基本概念了解线程的基本概念19.1.1 19.1.1 进程、线程和应用程序进程、线程和应用程序域域进程

◦应用程序在计算机上是以单独的进程运行的,每一个新启动的应用程序都会创建一个新的进程。

◦一个进程至少包含一个执行线程并有它自己的地址空间,其目的是将应用程序相互隔离,因为进程之间不能直接共享内存。

4

线程线程线程是操作系统分配处理器时间的基本单位。操作系统将运行时间分配给线程,而不是进程。

线程不能单独存在,要求拥有一个所有者进程。而一个进程可以包含至少一个线程,即主进程线程( Sub Main),在主进程线程退出时该进程被终止。

线程可以与同一进程中的其他线程共享内存线程还有一组关联的资源,包括它自己的异常处理程序、任务计划优先级和操作系统在给其他线程分配运行时间时保留的该线程的上下文信息。

5

应用程序域应用程序域应用程序域提供安全而通用的处理单元,公共语言运行库可使用它来提供应用程序之间的隔离。

可以在单个进程中运行几个应用程序域,而不会造成进程间调用或进程间切换等方面的额外开销。

6

三者之间的关系三者之间的关系

线程1 线程2

进程

应用程序域1

线程1 线程2

应用程序域2

远程通讯

7

19.1.2 19.1.2 线程限制线程限制任何操作系统对于每个进程上的线程数量均有一个限制。因为每个线程都需要消耗真实的物理资源,保存线程上下文信息需要占用内存。

下述情况最适合采用多线程技术开发。◦时间密集或处理密集的事务妨碍用户界面◦单独的事务必须等待外部资源,例如远程文件或 Internet 连接。

8

19.2 19.2 线程创建与管理线程创建与管理19.2.1 19.2.1 创建线程创建线程使用 System.Threading.Thread 类下面代码创建了一个新的线程,并在该线程上运行名为myTask的子过程。1 Dim myThread As New

System.Threading.Thread(AddressOf myTask)

2 myThread.Start( )

3 ' 这里的代码马上执行

9

19.2.2 19.2.2 线程控制与状态转线程控制与状态转换换

方法 说明

Start 线程开始运行

Sleep 暂停线程一段时间

Suspend 挂起线程(当线程到达安全点时暂停进程)

Abort 当线程到达安全点时停止线程

Resume 重新启动暂停的进程

Join引起当前进程等待另一个进程结束。如果设置了超时值,则线程在分配的时间内完成就返回

True

10

安全点安全点 Safe PointSafe Point安全点是指代码中公共语言运行库可以安全地执行自动垃圾回收的位置;垃圾回收是指释放不再使用的变量并回收内存的过程。

调用线程的 Abort 或 Suspend方法时,公共语言运行库将对代码进行分析,确定让线程停止运行的适当位置。

11

线程状态线程状态线程从创建到终止,一定处于某种状态中。线程状态是由

System.Threading.Thread.ThreadState属性来定义的。

Unstarted

Stopped

SuspendedRunni ngStart()

Abort()

Sl eep()

12

通过执行 Thread 类提供的用于控制单个线程的方法,线程的状态也会随之改变。

13

19.3 19.3 使用多线程组件轻松创使用多线程组件轻松创建多线程应用程序建多线程应用程序BackGroundWorkTimer组件

14

19.3.1 BackGroundWorker19.3.1 BackGroundWorker组件组件许多常执行的操作可能需要比较长的操作时间,这样的操作可能会导致用户界面在运行时挂起,要等待操作完成后才可以进行其他操作。长时间的等待延迟对用户来说是不可忍受的。

BackGroundWorker组件为该类问题提供了一个解决方案,即将耗时操作在不同于应用程序的主用户界面线程的另一线程上异步执行,也就是所谓的在后台执行。

15

常用属性常用属性

属性 说明

WorkerReportsProgress

辅助线程是否支持报告进度。该属性 为 True , 则BackGroundWorker 组 件 的ProgressChanged

WorkerSupportsCacellation 辅助线程是否支持取消

16

常用方法常用方法

方法 说明

RunWorkerAsync 开 始 异 步 执 行 。 该 方 法 触 发DoWork事件

ReportProgress 报告进度,参数是一个整型,表示进 度 信 息 。 该 方 法 触 发ProgressChanged事件

CancelAsync 停止并退出异步执行的线程

17

常用事件常用事件

事件 说明

DoWork 调用 RunWorkerAsync时发生该事件

RunWorkerCompleted

当后台操作已完成、被取消或引发异常时发生该事件

ProgressChanged 调用 ReportProgress时发生该事件

18

示例:计算示例:计算 FibonacciFibonacci 数数界面设计:

19

下面代码是窗体的 Load事件过程,实现控件的初始化。1 Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load2 lblMessage.Text = ""3 ProgressBar1.Visible = False4 btnCancel.Enabled = False5 BackgroundWorker1.WorkerReportsProgress = True6 BackgroundWorker1.WorkerSupportsCancellation = True7End Sub

单击【开始】按钮后,开始在另外一个线程上执行计算Fibonacci数的操作。

20

下面代码是【开始】按钮的 Click事件过程。

1 Private Sub btnStart_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnStart.Click2 txbNum.Enabled = False3 btnStart.Enabled = False4 btnCancel.Enabled = True5 lblMessage.Text = " 请稍后…… "6 ProgressBar1.Visible = True7 numberToCompute = CInt(txbNum.Text)8 ' 调用异步操作方法9 BackgroundWorker1.RunWorkerAsync(numberToCompute)10End Sub

第 9 行调用 BackGroundWorker组件的 RunWorkerAsync方法,表示开始一个异步操作,此方法触发 BackGroundWorker组件的 DoWork事件。这意味着,在本例中,计算 Fibonacci数的代码需要放在 DoWork事件的处理过程内。

21

下面代码是 BackGroundWorker组件的DoWork事件过程。1 Private numberToCompute As Integer = 02 Private highestPercentageReached As Integer = 03 4 Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork5 ' 得到触发该事件的 BackGroundWorker的实例6 Dim worker As BackgroundWorker = CType(sender, BackgroundWorker)7 ' 将计算结果赋值给 DoWorkEventArgs 的 Result属性。该属性在 RunWorkerCompleted的事件处理过程中可见8 e.Result = ComputeFibonacci(e.Argument, worker, e)9 End Sub

22

下面代码是 ComputeFibonacci 函数。

1 ' ComputeFibonacci 函数的功能是返回第 n 项的 Fibonacci数2 Function ComputeFibonacci(ByVal n As Integer, ByVal worker As BackgroundWorker, ByVal e As DoWorkEventArgs) As Long3 ' 参数 n必须在 0 到 91之间,因为第 92 项 Fibonacci数超出了 Long 型的表示范围4 If n < 0 OrElse n > 91 Then5 Return -16 End If7 ' 定义结果变量8 Dim result As Long = 09 ' 如果用户选择取消操作,即调用了 CancelAsync方法,此时CancellationPending属性设置为 true。10 ' 因此可以根据 CancellationPending属性来判断用于是否取消了操作。之后将 DoWorkEventArgs.Cancel 标志设为 True。

23

11 If worker.CancellationPending Then12 e.Cancel = True13 ' 如果没有取消,则进行递归计算14 Else15 If n < 2 Then16 result = 117 Else18 result = ComputeFibonacci(n - 1, worker, e) + ComputeFibonacci(n - 2, worker, e)19 End If20 ' 报告计算进度21 Dim percentComplete As Integer = CSng(n) / CSng(numberToCompute) * 10022 If percentComplete > highestPercentageReached Then23 highestPercentageReached = percentComplete24 worker.ReportProgress(percentComplete)25 End If26 End If27 ' 返回计算结果28 Return result29 End Function

24

下面代码是 BackgroundWorker 控件的ProgressChanged事件处理过程。

1 Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As System.Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged2 ' 设置进度条3 ProgressBar1.Value = e.ProgressPercentage4 End Sub

25

下面代码是 RunWorkerCompleted事件的处理过程。1 Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As System.Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted2 If (e.Error IsNot Nothing) Then3 ' 出现异常的情况4 lblMessage.Text = e.Error.Message5 ' 被取消的情况6 ElseIf e.Cancelled Then7 lblMessage.Text = "任务已取消! "8 ElseIf e.Result = -1 Then9 ' 参数超出范围的情况10 lblMessage.Text = " 请输入在 0 到 91之间的整数! "11 Else12 ' 任务正常完成的情况13 lblMessage.Text = " 已完成!结果为 " & e.Result.ToString()14 End If15 txbNum.Enabled = True16 btnStart.Enabled = True17 btnCancel.Enabled = False18 ProgressBar1.Visible = False19 End Sub

26

分析:由于任务发生异常、被取消或者正常完成,均会

触发 RunWorkerCompleted事件,因此在该事件的处理过程中,需要区分三种情况:如果出现异常,则RunWorkerCompletedEventArgs 类型的参数 e 的 Error属性包含了异常的信息;如果被取消,则 e 的 Cancelled属性被设置为 True ;如果正常完成,则 e 的 Result属性包含了计算结果。根据不同的情况分别设置 lblMessage 控件的 Text属性以完成结果显示。

27

运行结果运行结果

28

19.3.2 Timer19.3.2 Timer 组件组件该 Timer组件则位于

System.Timers 命名空间,称为服务器计时器。

位于工具箱组件选项卡中的 Time组件是一个实现按用户定义的时间间隔引发事件的计时器,位于System.Windows.Forms 命名空间,是一个专为单线程设计的计时器,称为 Windows计时器。

29

二者区别二者区别服务器计时器是为在多线程环境下与

辅助线程一起使用而设计的。由于二者使用不同的体系结构,因此基于服务器的计时器可能比 Windows 计时器精确得多。

服务器计时器可以在线程之间移动来处理引发的事件。

30

创建创建 Timer Timer 组件的实例组件的实例两种方法:

◦一是将其添加到工具箱中;◦二是通过代码创建 Timer组件的实例。

31

第一种方法第一种方法在【工具】菜单上单击【选择工具箱项】,在打开的【选择工具箱项】窗口中再单击【 .NET Framework组件】选项卡, ,勾选【 System.Timers 】命名空间中的【 Timer 】复选框。

32

之后,直接将 Timer 图标拖动到窗体上,即可为该窗体添加一个服务器计时器。

33

第二种方法第二种方法用编程方式创建 Timer组件的实例,

代码如下。1 Dim myTimer As New

System.Timers.Timer( )2 myTimer.Interval = 30003 myTimer.Enabled = True

34

常用属性常用属性

属性 说明

Interval 设置计时器间隔,单位为毫秒

Enabled 只是是否启用计时器,以定义的时间间隔触发事件

AutoReset

指 示计时器是否在启动后重新启动。当AutoReset设置为 false时, Timer 只在第一个 Interval 过后引发一次 Elapsed事件。若要保持以 Interval 时间间隔引发 Elapsed事件,请将 AutoReset 设置为 true。

35

唯一事件唯一事件

事件 说明

Elapsed

间隔已过时触发该事件。该事件在 ThreadPool线程上引发。如果Elapsed 事 件 的 处 理 时 间 比Interval 长 , 在 另 一 个 ThreadPool 线程上将会再次引发此事件。因此,该事件的处理程序应当是可重入的

36

常用方法常用方法

方法 说明

Start 通过将 Enabled 设置为 true 开始引发 Elapsed事件

Stop 通过将 Enabled设置为 false 停止引发 Elapsed事件

Close 释放由 Timer占用的资源。

37

示例:窗体渐变退出示例:窗体渐变退出界面设计:

38

在窗体加载的时候初始化计时器。下面代码是窗体的 Load事件过程,完成计时器的初始化。

1 Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load2 ' 初始化计时器3 Timer1.Interval = 1004 Timer1.AutoReset = True5 Timer1.Enabled = False6 End Sub

39

单击【渐变退出】按钮后,启动计时器。下面代码是【渐变退出】按钮的 Click事件过程,启动计时器。1 Private Sub btnClose_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnClose.Click2 btnClose.Enabled = False ' 将窗体关闭按钮设置为不可用,防治在渐变过程中用户再次单击【关闭】按钮。3 Timer1.Start() ' 启动 TIMER开始计时4 End Sub5 ' 计时器启动后,每个 100 毫秒便触发 Elapsed事件,该事件的处理过程如下6 Private Sub Timer1_Elapsed(ByVal sender As System.Object, ByVal e As System.Timers.ElapsedEventArgs) Handles Timer1.Elapsed7 Me.Opacity -= 0.07 ' 根据 timer的计时频率 (? 毫秒 )来不断使窗体透明度减少。8 If Me.Opacity <= 0.15 Then9 Timer1.Enabled = False ' 当窗体透明度小于 15%时停止计时,既停止 timer所在线程。10 Me.Close() ' 销毁窗体资源11 End If12 End Sub

40

运行结果运行结果窗体逐渐变得透明