一、线程与委托
1、为什么要用异步?
无论是MemoryStream,BufferedStream,FileStream数据流,一旦的读写开始,应用程序就会处于停滞状况。
直到数据读写完成,并返回。
文件数据的读写基本上是一种非常消耗资源的过程,处理的数据量越大,I/O对系统性能的影响就越明显。
为了避免长时间等待I/O操作使程序处理“瘫痪”状态,异步I/O就显得非常重要。
异步的实现就是使用一个新的线程来完成,主线程的任务并不影响,这样大大提高了程序的效能。
2、线程
每个程序有一个主线程,如果一个循环处于主线程中,程序在较长的循环,将出现“不响应”的情况。
线程在System.Threading中。线程创建可专用于一个功能块(方法、函数),
线程的开始用Start方法
线程的结束用Abort方法
下面感受一下线程作用:
窗体上添加两Button,两个TextBox,代码如下,点击Button1启动循环,接着点击Button2.
可以明显看到虽然点击了Button1,但TextBox1的内容并没有什么变化,同时,在点击Button2时,TextBox并没有内容显示。
这是因为线程正被循环一直占用,暂时无法响应Button2,直到循环完成后,它才终于忙过来处理Button2.
这会给用户造成“程序已经无响应、死了”的误会。
下面改善上面的做法,新建一个线程来专门处理循环,这样就不影响主线程响应Button2:
- ImportsSystem.Threading
- PublicClassForm1
- DimmythreadAsThread
- PrivateSubButton1_Click(senderAsObject,eAsEventArgs)HandlesButton1.Click
- mythread=NewThread(AddressOfShowNumber)'构造线程
- mythread.Name="myShowNumber"
- mythread.Start()'启动线程
- EndSub
- PrivateSubShowNumber()
- DimiAsInt32
- Fori=0To123451
- TextBox1.Text=i
- Next
- mythread.Abort()'终止线程
- EndSub
- PrivateSubButton2_Click(senderAsObject,eAsEventArgs)HandlesButton2.Click
- TextBox2.Text="终于出现奇迹"
- EndSub
- EndClass
然而一点击,发现出错,提示:线程间操作无效: 从不是创建控件“TextBox1”的线程访问它。
这是因为TextBox1是主线程中的,却在另一个新的线程中访问,这种是不安全的,相当于去别人房间使用电视机。
怎么办?这里可以用委托,委托能够进别人房间的人去使用电视机。
3、委托
委托的思想,就是自己不能干或不想干的事,委托另一个有能力或有权限的人去干那件事。
实际上,我们一直要用委托思想,比如基本类型的变量名。Dim i As Integer
i变量名就是相当于委托,实际上,一个变量代表的是指定内存地址中的值,如果不用变量名,就得实际上引用这个内存的地址。
而我们就用“变量名”来干操作这个地址里的东西。
除了变量名可以用委托一样,方法也可以用委托,这就是我们普通所说的委托。
定义和使用大致与变量名的方式一样:
(1)定义委托类型: Private Delegate Sub MyDelegate(byval k as int32) '参数多种,多个)
这里类似定义变量的类型一样。
(2)定义要赋的具体“值”: 这里的具体值,不是值,而是一个具体的方法,方法的形式必须与上面定义保持一致。就象变量名是整形时,赋值也应该是整形,而不是String.
例如:Private Sub YourSelfMethod(byval m as int32) '方法名自定,但形式与(1)保持一致。
(3)调用这个值: 也就是委托去办事。用Invoke方法:Control.Invoke(New MyDelegate(AddressOf YourSelfMethod),intValue)
这一步就把(1),(2)使用上了。
下面接着上面的例子,使用委托来调用Form1中的TextBox1.
- ImportsSystem.Threading
- PublicClassForm1
- DimmythreadAsThread
- PrivateDelegateSubVoidShow(ByRefiAsInt32)'定义要委托的类型
- PrivateSubButton1_Click(senderAsObject,eAsEventArgs)HandlesButton1.Click
- mythread=NewThread(AddressOfShowNumber)
- mythread.Name="myShowNumber"
- mythread.Start()
- EndSub
- PrivateSubShowNumber()
- DimiAsInt32
- Fori=0To123451
- 'TextBox1.Text=i
- Me.Invoke(NewVoidShow(AddressOfTureShowNumber),i)'用New构造委托,再用Invoke执行
- Next
- mythread.Abort()
- EndSub
- '新加入的被委托要做的事
- PrivateSubTureShowNumber(ByRefiAsInt32)
- TextBox1.Text=i
- EndSub
- PrivateSubButton2_Click(senderAsObject,eAsEventArgs)HandlesButton2.Click
- TextBox2.Text="终于出现奇迹"
- EndSub
- EndClass
点击Buttton1,可以看到因为新线程的使用,TextBox1中的数字一直在变量。
而且,同时点击Button2程序不会“死机”,很快地响应。
注意的是:因为线程的中止使用的是强制中断Abort,所以即时窗体会显示:
System.Threading.ThreadAbortException 类型的第一次机会异常在 mscorlib.dll中发生
这个不影响使用。
二、异步读写
异步I/O与同步I/O最大的不同在于: 同步I/O只有完成整个I/O操作后,程序才会进行下一步(所以这之前象死机一样)。
异步I/O在操作读写操作的同时,程序可以继续下一步工作,不影响程序其它执行。
简单地说,主线程和新线程各自执行,不相互影响。
即流程如下:
程序(主线程)在左边开始时,就建立了新线程进行异步读写。
在异步开始时,就传入了一个回调参数,这个用于异步完成时,自动调用这个参数所指的过程。
其中的IAsyncResult表示异步操作的状态。结束异步操作时需要这个参数。
一般我们在I/O操作时都是同步,异步在FileStream构造时就必须指明文件采用的异步方法:
- PublicSubNew(_
- pathAsString,_
- modeAsFileMode,_
- accessAsFileAccess,_
- shareAsFileShare,_
- bufferSizeAsInteger,_'缓冲大小
- useAsyncAsBoolean_'True为异步
- )
下面看一下异步操作的例子:
1、委托:只是为了在线程中调用窗体中的控件TextBox1来显示状态。
2、线程:是异步I/O的必要过程
3、回调函数:这是异步完成后,自动来通知或告之,异步I/O已经完成了(否则,怎么知道异步的结束呢?)
- ImportsSystem.IO
- ImportsSystem.Threading
- PublicClassForm1
- DimbtArray(15)AsByte
- DimfsAsFileStream
- DimmyThreadAsThread
- DimblnProcessAsBoolean'进程是否使用标志
- PrivateDelegateSubShowMyMessage(ByValstrAsString)'线程中无法调用窗体控件,用委托解决
- '启动写或读进程
- PrivateSubButton1_Click(senderAsObject,eAsEventArgs)HandlesButton1.Click
- TextBox1.Text=""
- Try
- IfRadioButton2.Checked=TrueThen'写选中
- myThread=NewThread(AddressOfWriteData)
- myThread.Name="WriteBulkData"
- myThread.Start()
- Else
- myThread=NewThread(AddressOfReadData)
- myThread.Name="ReadBulkData"
- myThread.Start()
- EndIf
- CatchexAsException
- MessageBox.Show(ex.Message)
- EndTry
- EndSub
- PrivateSubWriteData()
- Try
- fs=NewFileStream("D:\11.txt",FileMode.Open,FileAccess.Write,FileShare.Write,16,True)
- DimmyWCBAsNewAsyncCallback(AddressOfMyAsyncWriteCallBack)
- blnProcess=True
- fs.BeginWrite(btArray,12,myWCB,Nothing)
- ProcessMessage("Write")
- fs.Close()
- CatchexAsException
- MessageBox.Show(ex.Message)
- EndTry
- EndSub
- PrivateSubReadData()
- Try
- fs=NewFileStream("d:\11.txt",FileAccess.Read,FileShare.Read,True)
- DimmyRCBAsNewAsyncCallback(AddressOfMyAsyncReadCallBack)
- blnProcess=True
- fs.BeginRead(btArray,myRCB,Nothing)
- ProcessMessage("Read")
- fs.Close()
- CatchexAsException
- MessageBox.Show(ex.Message)
- EndTry
- EndSub
- PrivateSubMyAsyncWriteCallBack(ByValmyIarAsIAsyncResult)
- Thread.Sleep(50)
- blnProcess=False
- fs.EndWrite(myIar)
- '委托显示信息
- DimstrAsString="异步线程数据写入完成。"
- Me.Invoke(NewShowMyMessage(AddressOfShowMessage),str)
- EndSub
- PrivateSubMyAsyncReadCallBack(ByValmyIarAsIAsyncResult)
- Thread.Sleep(50)
- blnProcess=False
- fs.EndRead(myIar)
- '委托显示信息
- DimstrAsString="异步线程数据读取完成。"
- Me.Invoke(NewShowMyMessage(AddressOfShowMessage),str)
- EndSub
- PrivateSubShowMessage(ByValstrAsString)
- TextBox1.Text&=Now.ToString&str&vbCrLf
- EndSub
- PrivateSubProcessMessage(ByValstrRWAsString)
- DimstrMessageAsString=""
- IfstrRW="Read"Then
- strMessage="判断异步正在读取..."
- Else
- strMessage="判断异步正在写入..."
- EndIf
- DoWhileblnProcess=True
- Me.Invoke(NewShowMyMessage(AddressOfShowMessage),strMessage)
- Loop
- Thread.Sleep(50)
- strMessage="判断读写已经完成。"
- Me.Invoke(NewShowMyMessage(AddressOfShowMessage),strMessage)
- EndSub
- EndClass
上面通过一个循环不断判断异步进行得怎么样(实际上是用的全局blnProcess来判断)
因为是例子,数据量不大,所以在过程加加入Sleep来延迟异步还在进行中。
为了减少显示的信息,把时间延时量减小到50毫秒。