分享

笔记7:vb.net的异步读写数据流(使用线程、委托)

 百眼通 2014-10-31

 

           在理解异步读写前,了解一下线程和委托是必要的。


一、线程与委托


        1、为什么要用异步?

              无论是MemoryStream,BufferedStream,FileStream数据流,一旦的读写开始,应用程序就会处于停滞状况。

              直到数据读写完成,并返回。

              文件数据的读写基本上是一种非常消耗资源的过程,处理的数据量越大,I/O对系统性能的影响就越明显。

              

             为了避免长时间等待I/O操作使程序处理“瘫痪”状态,异步I/O就显得非常重要。


             异步的实现就是使用一个新的线程来完成,主线程的任务并不影响,这样大大提高了程序的效能。


        2、线程

              每个程序有一个主线程,如果一个循环处于主线程中,程序在较长的循环,将出现“不响应”的情况。


              线程在System.Threading中。线程创建可专用于一个功能块(方法、函数),

                                                                   线程的开始用Start方法

                                                                   线程的结束用Abort方法


            下面感受一下线程作用:


             窗体上添加两Button,两个TextBox,代码如下,点击Button1启动循环,接着点击Button2.   

  1. Public Class Form1  
  2.   
  3.     Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click  
  4.         Dim i As Int32  
  5.         For i = 0 To 123451  
  6.             TextBox1.Text = i  
  7.         Next  
  8.     End Sub  
  9.   
  10.     Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click  
  11.         TextBox2.Text = "终于出现奇迹"  
  12.     End Sub  
  13. End Class  

           

            可以明显看到虽然点击了Button1,但TextBox1的内容并没有什么变化,同时,在点击Button2时,TextBox并没有内容显示。

           这是因为线程正被循环一直占用,暂时无法响应Button2,直到循环完成后,它才终于忙过来处理Button2.

           这会给用户造成“程序已经无响应、死了”的误会。





           下面改善上面的做法,新建一个线程来专门处理循环,这样就不影响主线程响应Button2:

  1. Imports System.Threading  
  2. Public Class Form1  
  3.     Dim mythread As Thread  
  4.   
  5.     Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click  
  6.         mythread = New Thread(AddressOf ShowNumber) '构造线程  
  7.         mythread.Name = "myShowNumber"  
  8.         mythread.Start() '启动线程  
  9.     End Sub  
  10.   
  11.     Private Sub ShowNumber()  
  12.         Dim i As Int32  
  13.         For i = 0 To 123451  
  14.             TextBox1.Text = i  
  15.         Next  
  16.   
  17.         mythread.Abort() '终止线程  
  18.     End Sub  
  19.   
  20.     Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click  
  21.         TextBox2.Text = "终于出现奇迹"  
  22.     End Sub  
  23. 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.

  1. Imports System.Threading  
  2. Public Class Form1  
  3.     Dim mythread As Thread  
  4.     Private Delegate Sub VoidShow(ByRef i As Int32) '定义要委托的类型  
  5.   
  6.     Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click  
  7.         mythread = New Thread(AddressOf ShowNumber)  
  8.         mythread.Name = "myShowNumber"  
  9.         mythread.Start()  
  10.     End Sub  
  11.   
  12.     Private Sub ShowNumber()  
  13.         Dim i As Int32  
  14.         For i = 0 To 123451  
  15.             'TextBox1.Text = i  
  16.             Me.Invoke(New VoidShow(AddressOf TureShowNumber), i) '用New构造委托,再用Invoke执行  
  17.         Next  
  18.   
  19.         mythread.Abort()  
  20.     End Sub  
  21.   
  22.     '新加入的被委托要做的事  
  23.     Private Sub TureShowNumber(ByRef i As Int32)  
  24.         TextBox1.Text = i  
  25.     End Sub  
  26.   
  27.   
  28.     Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click  
  29.         TextBox2.Text = "终于出现奇迹"  
  30.     End Sub  
  31. 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构造时就必须指明文件采用的异步方法:

  1. Public Sub New ( _  
  2.     path As String, _  
  3.     mode As FileMode, _  
  4.     access As FileAccess, _  
  5.     share As FileShare, _  
  6.     bufferSize As Integer, _  '缓冲大小  
  7.     useAsync As Boolean _    'True为异步  
  8. )  


         下面看一下异步操作的例子:

                 1、委托:只是为了在线程中调用窗体中的控件TextBox1来显示状态。

                 2、线程:是异步I/O的必要过程

                 3、回调函数:这是异步完成后,自动来通知或告之,异步I/O已经完成了(否则,怎么知道异步的结束呢?)



  1. Imports System.IO  
  2. Imports System.Threading  
  3. Public Class Form1  
  4.     Dim btArray(15) As Byte  
  5.     Dim fs As FileStream  
  6.     Dim myThread As Thread  
  7.     Dim blnProcess As Boolean  '进程是否使用标志  
  8.   
  9.     Private Delegate Sub ShowMyMessage(ByVal str As String) '线程中无法调用窗体控件,用委托解决  
  10.   
  11.     '启动写或读进程  
  12.     Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click  
  13.         TextBox1.Text = ""  
  14.         Try  
  15.             If RadioButton2.Checked = True Then '写选中  
  16.                 myThread = New Thread(AddressOf WriteData)  
  17.                 myThread.Name = "WriteBulkData"  
  18.                 myThread.Start()  
  19.             Else  
  20.                 myThread = New Thread(AddressOf ReadData)  
  21.                 myThread.Name = "ReadBulkData"  
  22.                 myThread.Start()  
  23.             End If  
  24.         Catch ex As Exception  
  25.             MessageBox.Show(ex.Message)  
  26.         End Try  
  27.     End Sub  
  28.   
  29.     Private Sub WriteData()  
  30.         Try  
  31.             fs = New FileStream("D:\11.txt", FileMode.Open, FileAccess.Write, FileShare.Write, 16, True)  
  32.             Dim myWCB As New AsyncCallback(AddressOf MyAsyncWriteCallBack)  
  33.             blnProcess = True  
  34.             fs.BeginWrite(btArray, 0, 12, myWCB, Nothing)  
  35.             ProcessMessage("Write")  
  36.             fs.Close()  
  37.         Catch ex As Exception  
  38.             MessageBox.Show(ex.Message)  
  39.         End Try  
  40.     End Sub  
  41.   
  42.     Private Sub ReadData()  
  43.         Try  
  44.             fs = New FileStream("d:\11.txt", FileMode.Open, FileAccess.Read, FileShare.Read, 16, True)  
  45.             Dim myRCB As New AsyncCallback(AddressOf MyAsyncReadCallBack)  
  46.             blnProcess = True  
  47.             fs.BeginRead(btArray, 0, 16, myRCB, Nothing)  
  48.             ProcessMessage("Read")  
  49.             fs.Close()  
  50.         Catch ex As Exception  
  51.             MessageBox.Show(ex.Message)  
  52.         End Try  
  53.     End Sub  
  54.   
  55.     Private Sub MyAsyncWriteCallBack(ByVal myIar As IAsyncResult)  
  56.         Thread.Sleep(50)  
  57.         blnProcess = False  
  58.         fs.EndWrite(myIar)  
  59.   
  60.         '委托显示信息  
  61.         Dim str As String = "  异步线程数据写入完成。"  
  62.         Me.Invoke(New ShowMyMessage(AddressOf ShowMessage), str)  
  63.     End Sub  
  64.   
  65.     Private Sub MyAsyncReadCallBack(ByVal myIar As IAsyncResult)  
  66.         Thread.Sleep(50)  
  67.         blnProcess = False  
  68.         fs.EndRead(myIar)  
  69.   
  70.         '委托显示信息  
  71.         Dim str As String = "  异步线程数据读取完成。"  
  72.         Me.Invoke(New ShowMyMessage(AddressOf ShowMessage), str)  
  73.     End Sub  
  74.   
  75.     Private Sub ShowMessage(ByVal str As String)  
  76.         TextBox1.Text &= Now.ToString & str & vbCrLf  
  77.     End Sub  
  78.   
  79.   
  80.     Private Sub ProcessMessage(ByVal strRW As String)  
  81.         Dim strMessage As String = ""  
  82.         If strRW = "Read" Then  
  83.             strMessage = "  判断异步正在读取..."  
  84.         Else  
  85.             strMessage = "  判断异步正在写入..."  
  86.         End If  
  87.   
  88.         Do While blnProcess = True  
  89.             Me.Invoke(New ShowMyMessage(AddressOf ShowMessage), strMessage)  
  90.         Loop  
  91.         Thread.Sleep(50)  
  92.   
  93.         strMessage = "  判断读写已经完成。"  
  94.         Me.Invoke(New ShowMyMessage(AddressOf ShowMessage), strMessage)  
  95.     End Sub  
  96. End Class  

             

            上面通过一个循环不断判断异步进行得怎么样(实际上是用的全局blnProcess来判断)

            因为是例子,数据量不大,所以在过程加加入Sleep来延迟异步还在进行中。

            为了减少显示的信息,把时间延时量减小到50毫秒。





    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多