在理解异步读写前,了解一下线程和委托是必要的。
一、线程与委托
1、为什么要用异步?
无论是MemoryStream,BufferedStream,FileStream数据流,一旦的读写开始,应用程序就会处于停滞状况。
直到数据读写完成,并返回。
文件数据的读写基本上是一种非常消耗资源的过程,处理的数据量越大,I/O对系统性能的影响就越明显。
为了避免长时间等待I/O操作使程序处理“瘫痪”状态,异步I/O就显得非常重要。
异步的实现就是使用一个新的线程来完成,主线程的任务并不影响,这样大大提高了程序的效能。
2、线程
每个程序有一个主线程,如果一个循环处于主线程中,程序在较长的循环,将出现“不响应”的情况。
线程在System.Threading中。线程创建可专用于一个功能块(方法、函数),
线程的开始用Start方法
线程的结束用Abort方法
下面感受一下线程作用:
窗体上添加两Button,两个TextBox,代码如下,点击Button1启动循环,接着点击Button2.
- Public Class Form1
-
- Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
- Dim i As Int32
- For i = 0 To 123451
- TextBox1.Text = i
- Next
- End Sub
-
- Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
- TextBox2.Text = "终于出现奇迹"
- End Sub
- End Class
可以明显看到虽然点击了Button1,但TextBox1的内容并没有什么变化,同时,在点击Button2时,TextBox并没有内容显示。
这是因为线程正被循环一直占用,暂时无法响应Button2,直到循环完成后,它才终于忙过来处理Button2.
这会给用户造成“程序已经无响应、死了”的误会。
下面改善上面的做法,新建一个线程来专门处理循环,这样就不影响主线程响应Button2:
- Imports System.Threading
- Public Class Form1
- Dim mythread As Thread
-
- Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
- mythread = New Thread(AddressOf ShowNumber) '构造线程
- mythread.Name = "myShowNumber"
- mythread.Start() '启动线程
- End Sub
-
- Private Sub ShowNumber()
- Dim i As Int32
- For i = 0 To 123451
- TextBox1.Text = i
- Next
-
- mythread.Abort() '终止线程
- End Sub
-
- Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
- TextBox2.Text = "终于出现奇迹"
- End Sub
- End Class
然而一点击,发现出错,提示:线程间操作无效: 从不是创建控件“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.
- Imports System.Threading
- Public Class Form1
- Dim mythread As Thread
- Private Delegate Sub VoidShow(ByRef i As Int32) '定义要委托的类型
-
- Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
- mythread = New Thread(AddressOf ShowNumber)
- mythread.Name = "myShowNumber"
- mythread.Start()
- End Sub
-
- Private Sub ShowNumber()
- Dim i As Int32
- For i = 0 To 123451
- 'TextBox1.Text = i
- Me.Invoke(New VoidShow(AddressOf TureShowNumber), i) '用New构造委托,再用Invoke执行
- Next
-
- mythread.Abort()
- End Sub
-
- '新加入的被委托要做的事
- Private Sub TureShowNumber(ByRef i As Int32)
- TextBox1.Text = i
- End Sub
-
-
- Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
- TextBox2.Text = "终于出现奇迹"
- End Sub
- End Class
点击Buttton1,可以看到因为新线程的使用,TextBox1中的数字一直在变量。
而且,同时点击Button2程序不会“死机”,很快地响应。
注意的是:因为线程的中止使用的是强制中断Abort,所以即时窗体会显示:
System.Threading.ThreadAbortException 类型的第一次机会异常在 mscorlib.dll中发生
这个不影响使用。
二、异步读写
异步I/O与同步I/O最大的不同在于: 同步I/O只有完成整个I/O操作后,程序才会进行下一步(所以这之前象死机一样)。
异步I/O在操作读写操作的同时,程序可以继续下一步工作,不影响程序其它执行。
简单地说,主线程和新线程各自执行,不相互影响。
即流程如下:
程序(主线程)在左边开始时,就建立了新线程进行异步读写。
在异步开始时,就传入了一个回调参数,这个用于异步完成时,自动调用这个参数所指的过程。
其中的IAsyncResult表示异步操作的状态。结束异步操作时需要这个参数。
一般我们在I/O操作时都是同步,异步在FileStream构造时就必须指明文件采用的异步方法:
- Public Sub New ( _
- path As String, _
- mode As FileMode, _
- access As FileAccess, _
- share As FileShare, _
- bufferSize As Integer, _ '缓冲大小
- useAsync As Boolean _ 'True为异步
- )
下面看一下异步操作的例子:
1、委托:只是为了在线程中调用窗体中的控件TextBox1来显示状态。
2、线程:是异步I/O的必要过程
3、回调函数:这是异步完成后,自动来通知或告之,异步I/O已经完成了(否则,怎么知道异步的结束呢?)
- Imports System.IO
- Imports System.Threading
- Public Class Form1
- Dim btArray(15) As Byte
- Dim fs As FileStream
- Dim myThread As Thread
- Dim blnProcess As Boolean '进程是否使用标志
-
- Private Delegate Sub ShowMyMessage(ByVal str As String) '线程中无法调用窗体控件,用委托解决
-
- '启动写或读进程
- Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
- TextBox1.Text = ""
- Try
- If RadioButton2.Checked = True Then '写选中
- myThread = New Thread(AddressOf WriteData)
- myThread.Name = "WriteBulkData"
- myThread.Start()
- Else
- myThread = New Thread(AddressOf ReadData)
- myThread.Name = "ReadBulkData"
- myThread.Start()
- End If
- Catch ex As Exception
- MessageBox.Show(ex.Message)
- End Try
- End Sub
-
- Private Sub WriteData()
- Try
- fs = New FileStream("D:\11.txt", FileMode.Open, FileAccess.Write, FileShare.Write, 16, True)
- Dim myWCB As New AsyncCallback(AddressOf MyAsyncWriteCallBack)
- blnProcess = True
- fs.BeginWrite(btArray, 0, 12, myWCB, Nothing)
- ProcessMessage("Write")
- fs.Close()
- Catch ex As Exception
- MessageBox.Show(ex.Message)
- End Try
- End Sub
-
- Private Sub ReadData()
- Try
- fs = New FileStream("d:\11.txt", FileMode.Open, FileAccess.Read, FileShare.Read, 16, True)
- Dim myRCB As New AsyncCallback(AddressOf MyAsyncReadCallBack)
- blnProcess = True
- fs.BeginRead(btArray, 0, 16, myRCB, Nothing)
- ProcessMessage("Read")
- fs.Close()
- Catch ex As Exception
- MessageBox.Show(ex.Message)
- End Try
- End Sub
-
- Private Sub MyAsyncWriteCallBack(ByVal myIar As IAsyncResult)
- Thread.Sleep(50)
- blnProcess = False
- fs.EndWrite(myIar)
-
- '委托显示信息
- Dim str As String = " 异步线程数据写入完成。"
- Me.Invoke(New ShowMyMessage(AddressOf ShowMessage), str)
- End Sub
-
- Private Sub MyAsyncReadCallBack(ByVal myIar As IAsyncResult)
- Thread.Sleep(50)
- blnProcess = False
- fs.EndRead(myIar)
-
- '委托显示信息
- Dim str As String = " 异步线程数据读取完成。"
- Me.Invoke(New ShowMyMessage(AddressOf ShowMessage), str)
- End Sub
-
- Private Sub ShowMessage(ByVal str As String)
- TextBox1.Text &= Now.ToString & str & vbCrLf
- End Sub
-
-
- Private Sub ProcessMessage(ByVal strRW As String)
- Dim strMessage As String = ""
- If strRW = "Read" Then
- strMessage = " 判断异步正在读取..."
- Else
- strMessage = " 判断异步正在写入..."
- End If
-
- Do While blnProcess = True
- Me.Invoke(New ShowMyMessage(AddressOf ShowMessage), strMessage)
- Loop
- Thread.Sleep(50)
-
- strMessage = " 判断读写已经完成。"
- Me.Invoke(New ShowMyMessage(AddressOf ShowMessage), strMessage)
- End Sub
- End Class
上面通过一个循环不断判断异步进行得怎么样(实际上是用的全局blnProcess来判断)
因为是例子,数据量不大,所以在过程加加入Sleep来延迟异步还在进行中。
为了减少显示的信息,把时间延时量减小到50毫秒。
|