轻量级的异步程序

来源:互联网 发布:喵帕斯网络加速 编辑:程序博客网 时间:2024/05/16 15:23
轻量级的异步程序
.net框架提供了Thread类和ThreadPool类,对多线程进行支持。这两个类的功能是非常强大的,以至于当我们要设计轻量级的异步程序时颇有牛刀杀鸡之感。事实上,可以采用Invoke方法来完成这种轻量级的异步调用。涉及到的方法包括Invoke()、BeginInvoke()、EndInvoke(),以及接口IAsyncResult。
所谓的异步调用大体上可以这样描述:当一个异步调用发生时,该调用将立即调用一个方法并迅速返回。当那个异步执行流程执行期间,主执行流程依然可以继续执行。当然我们也可以根据需要阻塞主执行流程,直到完成异步处理为止。还可以为异步处理传递一个回调方法,在数据准备就绪时异步执行流程会通过回调方法发出通知。下面程序是一个最简单的异步调用:
    Private Delegate Sub MyDelegate()
    '将会异步调用此方法
    Private Sub Sub2()
        Me.RichTextBox2.Text &= "SubThread现在开始!"
        Dim i As Integer = 0
        While i < 100
            Me.RichTextBox2.Text &= "SubThread正在执行!" & i.ToString()
            i += 1
        End While
        Me.RichTextBox2.Text &= "SubThread已经结束!"
    End Sub
 
    '把这个方法作为主执行流程
    Private Sub Sub1()
        Me.RichTextBox1.Text &= "MainThread现在开始!"
        Dim i As Integer = 0
        While i < 100
            Me.RichTextBox1.Text &= "MainThread正在执行!" & i.ToString()
            i += 1
        End While
        Me.RichTextBox1.Text &= "MainThread已经结束!"
    End Sub
 
    Private Sub btnBegin_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnBegin.Click
        Me.RichTextBox1.Text = "来吧,哈哈!"
 
        Dim d As New MyDelegate(AddressOf Me.Sub2)
        '在创建控件的基础句柄所在线程上异步执行指定委托
        Me.BeginInvoke(d)
 
        Me.Sub1()
        '看到了吧,先执行了Sub2,但是同时Sub1也在执行。
    End Sub
下面是全部代码,以便参考。
 
Public Class Form1
    Inherits System.Windows.Forms.Form
 
#Region " Windows 窗体设计器生成的代码 "
 
    Public Sub New()
        MyBase.New()
 
        '该调用是 Windows 窗体设计器所必需的。
        InitializeComponent()
 
        '在 InitializeComponent() 调用之后添加任何初始化
 
    End Sub
 
    '窗体重写 dispose 以清理组件列表。
    Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
        If disposing Then
            If Not (components Is Nothing) Then
                components.Dispose()
            End If
        End If
        MyBase.Dispose(disposing)
    End Sub
 
    'Windows 窗体设计器所必需的
    Private components As System.ComponentModel.IContainer
 
    '注意: 以下过程是 Windows 窗体设计器所必需的
    '可以使用 Windows 窗体设计器修改此过程。
    '不要使用代码编辑器修改它。
    Friend WithEvents RichTextBox1 As System.Windows.Forms.RichTextBox
    Friend WithEvents btnBegin As System.Windows.Forms.Button
    Friend WithEvents RichTextBox2 As System.Windows.Forms.RichTextBox
    <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
        Me.RichTextBox1 = New System.Windows.Forms.RichTextBox
        Me.btnBegin = New System.Windows.Forms.Button
        Me.RichTextBox2 = New System.Windows.Forms.RichTextBox
        Me.SuspendLayout()
        '
        'RichTextBox1
        '
        Me.RichTextBox1.Anchor = CType((((System.Windows.Forms.AnchorStyles.Top Or System.Windows.Forms.AnchorStyles.Bottom) _
                    Or System.Windows.Forms.AnchorStyles.Left) _
                    Or System.Windows.Forms.AnchorStyles.Right), System.Windows.Forms.AnchorStyles)
        Me.RichTextBox1.Location = New System.Drawing.Point(0, 0)
        Me.RichTextBox1.Name = "RichTextBox1"
        Me.RichTextBox1.Size = New System.Drawing.Size(192, 216)
        Me.RichTextBox1.TabIndex = 0
        Me.RichTextBox1.Text = ""
        '
        'btnBegin
        '
        Me.btnBegin.Anchor = CType((System.Windows.Forms.AnchorStyles.Bottom Or System.Windows.Forms.AnchorStyles.Right), System.Windows.Forms.AnchorStyles)
        Me.btnBegin.Location = New System.Drawing.Point(308, 232)
        Me.btnBegin.Name = "btnBegin"
        Me.btnBegin.TabIndex = 1
        Me.btnBegin.Text = "来吧,哈哈!"
        '
        'RichTextBox2
        '
        Me.RichTextBox2.Location = New System.Drawing.Point(200, 0)
        Me.RichTextBox2.Name = "RichTextBox2"
        Me.RichTextBox2.Size = New System.Drawing.Size(192, 216)
        Me.RichTextBox2.TabIndex = 2
        Me.RichTextBox2.Text = ""
        '
        'Form1
        '
        Me.AutoScaleBaseSize = New System.Drawing.Size(6, 14)
        Me.ClientSize = New System.Drawing.Size(392, 266)
        Me.Controls.Add(Me.RichTextBox2)
        Me.Controls.Add(Me.btnBegin)
        Me.Controls.Add(Me.RichTextBox1)
        Me.Name = "Form1"
       Me.Text = "Form1"
        Me.ResumeLayout(False)
 
    End Sub
 
#End Region
 
    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
 
    End Sub
 
注意以下的程序:
    Private Delegate Sub MyDelegate()
 
    '将会异步调用此方法
    Private Sub Sub2()
        Me.RichTextBox2.Text &= "SubThread现在开始!"
        Dim i As Integer = 0
        While i < 100
            Me.RichTextBox2.Text &= "SubThread正在执行!" & i.ToString()
            i += 1
        End While
        Me.RichTextBox2.Text &= "SubThread已经结束!"
    End Sub
 
    '把这个方法作为主执行流程
    Private Sub Sub1()
        Me.RichTextBox1.Text &= "MainThread现在开始!"
        Dim i As Integer = 0
        While i < 100
            Me.RichTextBox1.Text &= "MainThread正在执行!" & i.ToString()
            i += 1
        End While
        Me.RichTextBox1.Text &= "MainThread已经结束!"
    End Sub
 
    Private Sub btnBegin_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnBegin.Click
        Me.RichTextBox1.Text = "来吧,哈哈!"
 
       Dim d As New MyDelegate(AddressOf Me.Sub2)
        '在创建控件的基础句柄所在线程上异步执行指定委托
        Me.BeginInvoke(d)
 
        Me.Sub1()
        '看到了吧,先执行了Sub2,但是同时Sub1也在执行。
    End Sub
End Class
虽然Sub2()方法是先调用的,但是在它没有执行完之前Sub1()方法也在执行。为了进行异步调用,我们使用了BeginInvoke()方法(如果想知道这个方法的更多相关信息,不妨在网上搜索以下)。该方法在创建控件的基础句柄所在线程上异步执行指定委托。通常这个方法有两个参数,其中一个是要执行的委托。可以定义一个委托的实例,并且把要执行的方法给那个委托的实例,然后把这个委托作为参数给BeginInvoke()方法。BeginInvoke()方法的第二个参数是一个数组,要求的是委托所要的参数。如果这个委托要求3个参数,那么就应该按照顺序把这3个参数赋值给数组的三个元素,然后把这个数组作为参数传递给BeginInvoke()方法的第二个参数。不啰嗦了,看程序吧:
下面这段程序对方法Sub1()和Sub2()进行了修改,而且还改动了MyDelegate。
Private Delegate Sub MyDelegate(ByVal i As Integer, ByVal str As String, ByVal b As Boolean)
 
    '将会异步调用此方法
    Private Sub Sub2(ByVal i As Integer, ByVal str As String, ByVal b As Boolean)
        Me.RichTextBox2.Text &= i.ToString & "      "
        Me.RichTextBox2.Text &= str.ToString & "        "
        Me.RichTextBox2.Text &= b.ToString & "      "
    End Sub
 
    '把这个方法作为主执行流程
    Private Sub Sub1()
        Me.RichTextBox1.Text = "执行了主运行流程!"
    End Sub
 
    Private Sub btnBegin_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnBegin.Click
        '首先new一个委托,并且把方法Sub2()给这个委托
        Dim d As New MyDelegate(AddressOf Me.Sub2)
        '看第二个参数是一个Object数组,数组的三个元素会被依次传递给Sub1()方法的三个参数
        Me.BeginInvoke(d, New Object() {5, "aaaaa", True})
        Me.Sub1()
    End Sub
上面的程序里委托MyDelegate要求三个参数,我们就把委托要求的参数给数组,然后把数组传递给方法BeginInvalidate()的第二个参数。如果感觉看文字叙述挺费劲就仔细看上面的程序吧。
如果异步调用的方法和委托有返回值,那么该怎么得到这个返回值呢?遇到这种情况恐怕需要使用轮讯和阻塞技术。如下所示:
 
    '这个委托有一个返回值
    Private Delegate Function MyDelegate(ByVal str As String) As String
 
    '这个方法有一个返回值
    Private Function fun(ByVal str As String) As String
        Return str
    End Function
 
    Private Sub btnBegin_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnBegin.Click
        '定义一个委托对象,并且把方法fun的地址给这个对象
        Dim d As New MyDelegate(AddressOf fun)
        '调用BeginInvoke()方法异步执行这个委托,
        '并且把该方法的返回值给result
        Dim result As IAsyncResult = _
            Me.BeginInvoke(d, New Object() {"一个字符串"})
 
        '轮讯result的Iscompleted属性,该属性为true说明异步调用已经完成
        While (Not result.IsCompleted)
            Application.DoEvents()
        End While
 
        '当result的Iscompleted属性为true的时候
        '调用EndInvoke()方法,得到的返回值就是fun()方法的返回值
        Me.Label1.Text = Me.EndInvoke(result)
    End Sub
End Class
方法EndInvoke()的作用按照微软的标准说法是:检索由传递的IAsyncResult对象表示异步操作的返回值。根据我的理解,异步调用执行了哪个委托,EndInvoke()方法就返回那个委托的返回值。IAsyncResult.IsCompleted属性表示异步调用是否完成,true完成,false就是没完成。
如果实在不想用轮讯,也可以让程序等待一小会,如下所示:
Private Sub btnBegin_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnBegin.Click
        '定义一个委托对象,并且把方法fun的地址给这个对象
        Dim d As New MyDelegate(AddressOf fun)
        '调用BeginInvoke()方法异步执行这个委托,
        '并且把该方法的返回值给result
        Dim result As IAsyncResult = _
            Me.BeginInvoke(d, New Object() {"一个字符串"})
 
        '让程序等待一小会
        result.AsyncWaitHandle.WaitOne(5000, False)
 
        '调用EndInvoke()方法,得到的返回值就是fun()方法的返回值
        Me.Label1.Text = Me.EndInvoke(result)
    End Sub
关于IAsyncResult还提供了很多不错的功能,如果想要了解更多就在网上搜索喽。看到了吧,轻量级的异步调用非常简单,一点都不复杂。事实上在.net平台上真正的多线程程序也同样不难。
请参阅:
多线程
真正的多线程