您的位置:首页 > 理论基础 > 计算机网络

TCPIP通讯程序的编写

2008-11-18 16:01 513 查看
欢迎光临Sdhjt的窝,本篇心得除参考的部分文档外属于原创,转载请注明出处,谢谢!
作者:Sdhjt
一、总体介绍
  网络通信最好用的还是套接字(Sockets),.NET提供了Sockets的托管实现。命名空间是System.Net.Sockets,而在这个命名空间中常用的有三个类:TcpClient、TcpListener 和 UdpClient 类。
TcpListener:监听类,一般用作服务器端监听端口使用。
TcpClient:客户端类,一般用作客户端连接服务器使用。在服务器中用作管理客户端连接。每一个TcpClient代表一个客户端的TCP连接。
UdpClient:UDP客户端类,一般用作客户端连接服务器使用。在服务器中用作管理客户端连接。每一个UdpClient代表一个客户端的UDP连接。
二、流程介绍
1、典型监听代码的编写

'设置/判断监听是否开始的标志
Public IsStart As Boolean = False
'监听类的实例,用于监听某个端口
Private Listener As TcpListener
'存储监听的端口
Public ListenPort As UInteger
'存储监听的本机IP地址
Public LocalIP As Net.IPAddress

'线程类的实例,监听工作是在单独的线程上完成的
Private listenerThread As Threading.Thread

'开始监听,当不在进行监听的时候,需要调用:StopListen
Public Sub StartListen(ByVal pListenPort As UInteger, ByVal pLocalIP As String)

If IsStart = False Then
Try
'将字符型的IP地址转换为Net.IPAddress类型
LocalIP = Net.IPAddress.Parse(pLocalIP)
ListenPort = pListenPort

'开启新的线程,回调DoListen过程
listenerThread = New Threading.Thread(AddressOf DoListen)
'整个监听代码都在DoListen过程中完成,从而不影响主线程
listenerThread.Start()
UpdateStatus("监听开始。")
IsStart = True
Catch
UpdateStatus("监听失败。可能是错误的本机地址。")
End Try
End If
End Sub

'结束监听
Public Sub StopListen()
If IsStart = True Then
Listener.Stop()
listenerThread.Abort()
IsStart = False
End If
End Sub

'用于进行监听新连接的子程序,将会被独立线程执行。参数分别为:监听端口,本机IP
Private Sub DoListen()
Try
'监听新的连接
Dim ServerAddress As Net.IPAddress
'设置要监听的本机IP
ServerAddress = LocalIP
'实例化监听类
Listener = New TcpListener(ServerAddress, ListenPort)
'正式开始监听
Listener.Start()
Do
'当监听到新的连接时,TcpListener.AcceptTcpClient()返回一个TcpClient
'代表这个新的连接。TcpListener.AcceptTcpClient()是阻塞方法,程序执行到
' TcpListener.AcceptTcpClient()将暂停,直到有一个新的连接传入。
' UserConnection类是自定义的对TcpClient进一步封装的类。
Dim client As New UserConnection(Listener.AcceptTcpClient)

'添加事件句柄,本连接的所有传入消息都由OnLineReceived处理
AddHandler client.LineReceived, AddressOf OnLineReceived
UpdateStatus("发现新的连接,等待对方回应。")
Loop Until False
Catch
End Try
End Sub

2、对TcpClient的封装
监听到的每一个新的连接,都会由TcpListener.AcceptTcpClient()返回一个TcpClient,这个类的实例就代表这个连接,所有的数据发送和接收都有这个类完成。下面是一个自定义的类:UserConnection类,这个类完成对TcpClient的进一步封装。

Imports System.Net.Sockets
Imports System.Text

' The UserConnection class encapsulates the functionality of a TcpClient connection
' with streaming for a single user.
Public Class UserConnection
Const READ_BUFFER_SIZE As Integer = 255

' Overload the New operator to set up a read thread.
Public Sub New(ByVal client As TcpClient)
Me.client = client

' This starts the asynchronous read thread. The data will be saved into
' readBuffer.
Me.client.GetStream.BeginRead(readBuffer, 0, READ_BUFFER_SIZE, AddressOf StreamReceiver, Nothing)
End Sub

Private client As TcpClient
Private readBuffer(READ_BUFFER_SIZE) As Byte
Private strName As String

' The Name property uniquely identifies the user connection.
Public Property Name() As String
Get
Return strName
End Get
Set(ByVal Value As String)
strName = Value
End Set
End Property

Public Event LineReceived(ByVal sender As UserConnection, ByVal Data As String)

' This subroutine uses a StreamWriter to send a message to the user.
Public Sub SendData(ByVal Data As String)
' Synclock ensure that no other threads try to use the stream at the same time.
SyncLock client.GetStream
Dim writer As New IO.StreamWriter(client.GetStream)
writer.Write(Data & Chr(13) & Chr(10))

' Make sure all data is sent now.
writer.Flush()
End SyncLock
End Sub

' This is the callback function for TcpClient.GetStream.Begin. It begins an
' asynchronous read from a stream.
Private Sub StreamReceiver(ByVal ar As IAsyncResult)
Dim BytesRead As Integer
Dim strMessage As String

Try
' Ensure that no other threads try to use the stream at the same time.
SyncLock client.GetStream
' Finish asynchronous read into readBuffer and get number of bytes read.
BytesRead = client.GetStream.EndRead(ar)
End SyncLock

' Convert the byte array the message was saved into, minus one for the
' Chr(13).
strMessage = Encoding.ASCII.GetString(readBuffer, 0, BytesRead - 1)
RaiseEvent LineReceived(Me, strMessage)

' Ensure that no other threads try to use the stream at the same time.
SyncLock client.GetStream
' Start a new asynchronous read into readBuffer.
client.GetStream.BeginRead(readBuffer, 0, READ_BUFFER_SIZE, AddressOf StreamReceiver, Nothing)
End SyncLock
Catch e As Exception
End Try
End Sub
End Class

以上是原始的代码,不具备容错能力,因此出现以外的传输错误以后很容易导致程序崩溃。下面代码是我修改后的:

Imports System.Net.Sockets
Imports System.Text

'UserConnection类实现对TcpClient的进一步封装,使其满足各个用户间保持独立的要求(每一个UserConnection代表一个连接)。
Public Class UserConnection
Const READ_BUFFER_SIZE As Integer = 1023

'选手信息结构
Public Structure UserDataStructure
'存储用户的成绩信息
Public Score As Integer
'用户是否为评委,是则为真,否则为假
Public IsReg As Boolean
'用户是否响应消息(给客户端命令,客户端是否有回应)。0为正确,非零为出现某个错误。
Public Response As String
End Structure

'重载New方法建立一个读取线程。
Public Sub New(ByVal client As TcpClient)
Me.client = client

'建立一个异步的读取线程,数据将存储在readBuffer中。
Me.client.GetStream.BeginRead(readBuffer, 0, READ_BUFFER_SIZE, AddressOf StreamReceiver, Nothing)
End Sub

Private client As TcpClient
Private readBuffer(READ_BUFFER_SIZE) As Byte
Private strName As String
'存储客户端的其他信息
Public clientInfo As UserDataStructure

'在本类中约定Name是用户的唯一标识符(即Name不能有重复)。
Public Property Name() As String
Get
Return strName
End Get
Set(ByVal Value As String)
strName = Value
End Set
End Property

'定义UserConnection的事件,此事件在收到信息后发生
Public Event LineReceived(ByVal sender As UserConnection, ByVal Data As String)

'建立一个StreamWriter向客户端发送消息。
Public Sub SendData(ByVal Data As String)
Try
'用同步锁防止其它线程在此时也调用stream造成冲突。
SyncLock client.GetStream
Dim writer As New IO.StreamWriter(client.GetStream, Encoding.GetEncoding("GB2312"))
writer.Write(Data & Chr(13) & Chr(10))

'确保所有的数据传送完毕。此方法阻塞,等待所有数据传送完毕才返回。
writer.Flush()
End SyncLock
Catch e As Exception
'出错说明连接出问题了,断开连接。这里有可能出现的错误是,意外连接断开造成无法完成数据传输(如:突然把网线拔掉。)
client.Close()
'向外部传送一个断开连接的命令,这里是人为的。
RaiseEvent LineReceived(Me, "DISCONNECT")

End Try
End Sub

'此回调函数用于TcpClient.GetStream.Begin。它开始于一个异步读取流。
Private Sub StreamReceiver(ByVal ar As IAsyncResult)
'Dim i As Integer = 0
Dim BytesRead As Integer
Dim strMessage As String

Try
' Ensure that no other threads try to use the stream at the same time.
SyncLock client.GetStream
' Finish asynchronous read into readBuffer and get number of bytes read.
'完成异步读取。异步读取的使用方法可以参阅以后的详细内容。
BytesRead = client.GetStream.EndRead(ar)
End SyncLock

If BytesRead < 1 Then

' If no bytes were read server has close. Disable input window.
'已经断开连接
'人工造成错误,捕获后即可执行断开连接指令
Throw New System.Exception("An exception has occurred.")
Else
' Convert the byte array the message was saved into, minus one for the
' Chr(13).
'将字节数组转换为字符串,并减去最后一个Chr(13)字符。
strMessage = Encoding.GetEncoding("GB2312").GetString(readBuffer, 0, BytesRead - 1)

If strMessage = "DISCONNECT" Then
'人工造成错误,捕获后即可执行断开连接指令
Throw New System.Exception("An exception has occurred.")
End If
End If

RaiseEvent LineReceived(Me, strMessage)

' Ensure that no other threads try to use the stream at the same time.
SyncLock client.GetStream
' Start a new asynchronous read into readBuffer.
'新建一个异步读取线程。由于前面的异步读取线程已经完成工作,所以这里新建一个。
client.GetStream.BeginRead(readBuffer, 0, READ_BUFFER_SIZE, AddressOf StreamReceiver, Nothing)
End SyncLock
Catch e As Exception
'出错说明连接出问题了,断开连接
strMessage = "DISCONNECT"
client.Close()
RaiseEvent LineReceived(Me, strMessage)
End Try
End Sub
End Class

最后就是每个UserConnection的LineReceived事件的处理。我们可以将所有的UserConnection实例的事件处理过程都指定为OnLineReceived。下面就是OnLineReceived的代码,这些代码中相当一部分是在实际中使用的例子:

'UserConnection类的LineReceived事件处理子程序,当收到命令时使用。
Private Sub OnLineReceived(ByVal sender As UserConnection, ByVal data As String)
Dim i As Integer
Dim dataArray() As String

'实际应用中规定"|"为消息分隔符。这里是以"|"为分隔,将各个部分数据存入数组
dataArray = data.Split(Chr(124))

' dataArray(0)是消息中的命令部分
Select Case dataArray(0)
'用户连接服务器
Case "CONNECT"
Select Case dataArray(1)
'选手
Case "CLIENT"
ConnectUser(dataArray(2), sender)
'评委
Case "REG"
ConnectReg(dataArray(2), sender)
End Select

'Case "CHAT"
' SendChat(dataArray(1), sender)
'用户注销
Case "DISCONNECT"
DisconnectUser(sender)

………………………………………………

'给指定客户端发送目前分数
Case "GET_SCORE"
sender.SendData("SCORE_NOW|" & sender.clientInfo.Score.ToString)
Case Else
UpdateStatus("Unknown message:" & data)
End Select

End Sub

这是一个缩减版本的代码,在建立连接后,用户发送“CONNECT”命令,表示明确的建立连接(之前只是建立了TCP连接,可以进行通讯了。“CONNECT”属于程序员定义的命令)。此时代码执行ConnectUser或者ConnectReg过程(这些都是具体的例子,所以代码不抽象)。下面是ConnectUser的代码:

' This subroutine checks to see if username already exists in the clients
' Hashtable. If it does, send a REFUSE message, otherwise confirm with a JOIN.
Private Sub ConnectUser(ByVal userName As String, ByVal sender As UserConnection)

If Clients.Contains(userName) Then
ReplyToSender("REFUSE", sender)
Else
sender.Name = userName

'初始化用户信息
With sender.clientInfo
.IsReg = False
.Response = "-1"
.Score = 1000
End With

UpdateStatus(userName & "已连接到服务器。")
'将当前UserConnection类的一个实例加入到集合中去
Clients.Add(userName, sender)

' Send a JOIN to sender, and notify all other clients that sender joined
ReplyToSender("JOIN", sender)
SendToClients("CHAT|" & sender.Name & "已连接到服务器。")
End If
End Sub

这里涉及到一个很重要的概念就是哈希表数组,首先我们先解释这个表是干什么的。我们建立了很多的UserConnection类的实例,但是我们没有一个集合去容纳他们。这样当我们想对特定的UserConnection类的实例进行操作的时候,会发现根本无法明确找到特定的UserConnection。所以我们需要建立一个集合,容纳所有的UserConnection类的实例,这个集合就是这里所说的哈希表数组,而这个数组支持通过枚举的方式找到某个特定的类的实例。在程序中我们这样定义了这个数组:
'存储所有的有效客户端连接
Public Clients As New Hashtable()
我们再看看怎样对它进行常规操作。
A、插入操作
Clients.Add(userName, sender)
这条语句将名称为userName的sender实例添加到了Clients中。根据Add的参数的定义可知,第一个参数为Key,即关键字,这个应该是唯一的(人为规定,重复了也不会出错,但是没有意义)。第二个就是要添加的Object了,这里具体的就是UserConnection的一个实例。两个参数都是Object,可以容纳各种各样的对象。
B、检索操作
以下是典型的枚举操作代码。
Dim TmpClient As UserConnection
Dim entry As DictionaryEntry
For Each entry In Clients
'这里需要进行类型转换,每一个entry都是集合里的一个元素。
TmpClient = CType(entry.Value, UserConnection)
If TmpClient.clientInfo.Response <> "0" Then
i = -1
End If
Next
C、判断是否有特定的关键字
既然集合通过关键字进行索引,如果想判断集合中是否有某个特定的关键字,可以使用以下语句:
Clients.Contains(userName)
返回值为布尔值。
D、移除某个元素
Clients.Remove(userName)
语句简单明了,只要指定要删除的元素的关键字就可以了。

以上这些,就是对类的实例的存储方式的介绍。言归正传,以上我们已经能建立一个连接,并且可以传输数据了。那如何关闭一个连接呢?看以下代码:
' This subroutine notifies other clients that sender left the chat, and removes
' the name from the clients Hashtable
Private Sub DisconnectUser(ByVal sender As UserConnection)

UpdateStatus(sender.Name & "已经断开与服务器的连接。")

Dim client As UserConnection
Dim entry As DictionaryEntry

Try
'是否为评委
If sender.clientInfo.IsReg = True Then
RegClients.Remove(sender.Name)
Else
Clients.Remove(sender.Name)
End If

Catch ex As Exception
'客户端直接退出,有可能出现sender已经销毁的情况,所以加错误陷阱
End Try

SendToClients("CHAT|" & sender.Name & "已经断开与服务器的连接。")

End Sub
如果连接已经销毁,会触发异常,这里需要注意。

3、下面总结一下具体的流程:
a、用StartListen建立新的线程DoListen用于监听特定端口。
b、DoListen监听到有新的连接,建立一个TcpClient。然后建立一个UserConnection对TcpClient进行封装。
c、UserConnection开始进行工作,SendData方法用于传送数据,LineReceived事件用于接收数据。
d、重复b直到程序结束。用哈希表存储所有UserConnection类的实例。
e、断开连接。
f、停止监听。

其实经过.NET封装以后,这些底层的东西变得非常好用了。

4、一些补充
上面的内容以代码为主,毕竟代码能直观的表现出要表达的意思。这里还有一个问题是关于流操作的。在上面代码中,我们用到了异步的流读取和流写入。
首先是建立一个异步读取线程:
Me.client.GetStream.BeginRead(readBuffer, 0, READ_BUFFER_SIZE, AddressOf StreamReceiver, Nothing)
  这样,这个线程将调用StreamReceiver进行流的读取。在StreamReceiver中,我们首先调用了EndRead完成异步读取。如下面代码:

SyncLock client.GetStream
'完成异步读取。
BytesRead = client.GetStream.EndRead(ar)
End SyncLock

  EndRead方法是阻塞的,所以如果没有完成读取,则线程将等待。读取完成后将返回读取的字节数。还要注意的是,当一次读取结束后,需要再新建一个异步读取。因此StreamReceiver的最后我们有了以下代码:

SyncLock client.GetStream
'新建一个异步读取线程。由于前面的异步读取线程已经完成工作,所以这里新建一个。
client.GetStream.BeginRead(readBuffer, 0, READ_BUFFER_SIZE, AddressOf StreamReceiver, Nothing)
End SyncLock

关于异步编程的等多内容,MSDN中讲的非常详细了,在此复制几篇过来:

ms-help://MS.MSDNQTR.v80.chs/MS.MSDN.v80/MS.VisualStudio.v80.chs/dv_fxadvance/html/c9b3501e-6bc6-40f9-8efd-4b6d9e39ccf0.htm
异步编程概述
使用 IAsyncResult 设计模式的异步操作是通过名为 BeginOperationName 和 EndOperationName 的两个方法来执行的,这两个方法分别开始和结束异步操作 OperationName。例如,FileStream 类提供 BeginRead 和 EndRead 方法来从文件异步读取字节。这两个方法实现了 Read 方法的异步版本。
在调用 BeginOperationName 后,应用程序可以继续在调用线程上执行指令,同时异步操作在另一个线程上执行。每次调用 BeginOperationName 时,应用程序还应调用 EndOperationName 来获取操作的结果。
开始异步操作
Begin OperationName 方法开始异步操作 OperationName 并返回一个实现 IAsyncResult 接口的对象。IAsyncResult 对象存储有关异步操作的信息。下表提供了有关异步操作的信息。
成员 说明
AsyncState 一个可选的应用程序特定的对象,包含有关异步操作的信息。
AsyncWaitHandle 一个 WaitHandle,可用来在异步操作完成之前阻止应用程序执行。
CompletedSynchronously 一个值,指示异步操作是否是在用于调用 BeginOperationName 的线程上完成,而不是在另一个 ThreadPool 线程上完成。
IsCompleted 一个值,指示异步操作是否已完成。
Begin OperationName 方法带有该方法(由值传递或由引用传递)的同步版本的签名中声明的任何参数。BeginOperationName 方法签名中不包含任何输出参数。BeginOperationName 方法签名另外还包括两个参数。在这两个参数中,第一个参数定义一个 AsyncCallback 委托,此委托引用在异步操作完成时调用的方法。如果调用方不希望在操作完成后调用方法,它可以指定 null(在 Visual Basic 中为 Nothing)。第二个参数是一个用户定义的对象。此对象可用来向异步操作完成时调用的方法传递应用程序特定的状态信息。如果 BeginOperationName 方法还带有其他一些操作特定的参数(例如,一个用于存储从文件读取的字节的字节数组),则AsyncCallback 和应用程序状态对象将是 BeginOperationName 方法签名中的最后两个参数。
开始 OperationName 立即返回对调用线程的控制。如果 BeginOperationName 方法引发异常,则会在开始异步操作之前引发异常。如果 BeginOperationName 方法引发异常,则意味着没有调用回调方法。
结束异步操作
End OperationName 方法可结束异步操作 OperationName。EndOperationName 方法的返回值与其同步副本的返回值类型相同,并且是特定于异步操作的。例如,EndRead 方法返回从 FileStream 读取的字节数,EndGetHostByName 方法返回包含有关主机的信息的 IPHostEntry 对象。EndOperationName 方法带有该方法同步版本的签名中声明的所有输出参数或引用参数。除了来自同步方法的参数外,EndOperationName 方法还包括 IAsyncResult 参数。调用方必须将对应调用返回的实例传递给 BeginOperationName
如果调用 EndOperationName 时 IAsyncResult 对象表示的异步操作尚未完成,则 EndOperationName 将在异步操作完成之前阻止调用线程。异步操作引发的异常是从 EndOperationName 方法引发的。未定义多次使用同一 IAsyncResult 调用 EndOperationName 方法的效果。同样,也未定义使用非相关的 Begin 方法返回的 IAsyncResult 调用 EndOperationName 方法的效果。
注意
对于这两种未定义的情况,实施者应考虑引发 InvalidOperationException。
注意
此设计模式的实施者应通知调用方异步操作已通过以下步骤完成:将 IsCompleted 设置为 true,调用异步回调方法(如果已指定一个回调方法),然后发送 AsyncWaitHandle 信号。
对于访问异步操作的结果,应用程序开发人员有若干种设计选择。正确的选择取决于应用程序是否有可以在操作完成时执行的指令。如果应用程序在接收到异步操作结果之前不能进行任何其他工作,则必须先阻止该应用程序进行其他工作,等到获得这些操作结果后再继续进行。若要在异步操作完成之前阻止应用程序,您可以使用下列方法之一:
· 从应用程序的主线程调用 EndOperationName,阻止应用程序执行,直到操作完成之后再继续执行。有关演示此方法的示例,请参见通过结束一个异步操作来阻止应用程序的执行
· 使用 AsyncWaitHandle 来阻止应用程序执行,直到一个或多个操作完成之后再继续执行。有关演示此方法的示例,请参见使用 AsyncWaitHandle 阻止应用程序的执行
在异步操作完成时不需要阻止的应用程序可使用下列方法之一:
· 按以下方式轮询操作完成状态:定期检查 IsCompleted 属性,操作完成后调用 EndOperationName。有关演示此方法的示例,请参见轮询异步操作的状态
· 使用 AsyncCallback 委托来指定操作完成时要调用的方法。有关演示此方法的示例,请参见使用 AsyncCallback 委托结束一个异步操作
请参见
概念
使用异步方式调用同步方法
使用 AsyncCallback 委托和状态对象
其他资源
异步编程设计模式

ms-help://MS.MSDNQTR.v80.chs/MS.MSDN.v80/MS.VisualStudio.v80.chs/dv_fxadvance/html/41972034-92ed-450a-9664-ab93fcc6f1fb.htm
使用异步方式调用同步方法
.NET Framework 允许您异步调用任何方法。为此,应定义与您要调用的方法具有相同签名的委托;公共语言运行库会自动使用适当的签名为该委托定义 BeginInvoke 和 EndInvoke 方法。
BeginInvoke 方法可启动异步调用。它与您需要异步执行的方法具有相同的参数,另外它还有两个可选参数。第一个参数是一个 AsyncCallback 委托,该委托引用在异步调用完成时要调用的方法。第二个参数是一个用户定义的对象,该对象可向回调方法传递信息。BeginInvoke 立即返回,不等待异步调用完成。BeginInvoke 会返回 IAsyncResult,这个结果可用于监视异步调用进度。
EndInvoke 方法检索异步调用的结果。调用 BeginInvoke 后可随时调用 EndInvoke 方法;如果异步调用尚未完成,EndInvoke 将一直阻止调用线程,直到异步调用完成后才允许调用线程执行。EndInvoke 的参数包括您需要异步执行的方法的 out 和 ref 参数(在 Visual Basic 中为 <Out> ByRef 和 ByRef)以及由 BeginInvoke 返回的 IAsyncResult。
注意
Visual Studio 2005 中的 IntelliSense 功能显示 BeginInvoke 和 EndInvoke 的参数。如果您没有使用 Visual Studio 或类似工具,或您使用的是带有 Visual Studio 2005 的 C#,请参见异步编程概述以获取为这些方法定义的参数的说明。
本主题中的代码示例演示了四种使用 BeginInvoke 和 EndInvoke 进行异步调用的常用方法。调用 BeginInvoke 之后,您可以执行下列操作:
· 进行某些操作,然后调用 EndInvoke 一直阻止到调用完成。
· 使用 System.IAsyncResult.AsyncWaitHandle 属性获取 WaitHandle,使用它的 WaitOne 方法一直阻止执行直到发出 WaitHandle 信号,然后调用 EndInvoke。
· 轮询由 BeginInvoke 返回的 IAsyncResult,确定异步调用何时完成,然后调用 EndInvoke。
· 将用于回调方法的委托传递给 BeginInvoke。异步调用完成后,将在 ThreadPool 线程上执行该方法。该回调方法将调用 EndInvoke。
要点
每次都要调用 EndInvoke 来完成异步调用。
定义测试方法和异步委托
下面的代码示例演示异步调用同一个长时间运行的方法 TestMethod 的各种方式。TestMethod 方法会显示一条控制台消息,说明它已开始处理,休眠了几秒钟,然后结束。TestMethod 有一个 out 参数,该参数用于演示此种参数添加到 BeginInvoke 和 EndInvoke 的签名中的方式。您可以按同样的方式处理 ref 参数。
下面的代码示例演示 TestMethod 的定义和名为 AsyncMethodCaller 的、可用来异步调用 TestMethod 的委托。若要编译任何代码示例,必须包括 TestMethod 的定义和 AsyncMethodCaller 委托。
Visual Basic 复制代码
Imports System
Imports System.Threading
Imports System.Runtime.InteropServices

Namespace Examples.AdvancedProgramming.AsynchronousOperations
Public Class AsyncDemo
' The method to be executed asynchronously.
Public Function TestMethod(ByVal callDuration As Integer, _
<Out> ByRef threadId As Integer) As String
Console.WriteLine("Test method begins.")
Thread.Sleep(callDuration)
threadId = Thread.CurrentThread.ManagedThreadId()
return String.Format("My call time was {0}.", callDuration.ToString())
End Function
End Class

' The delegate must have the same signature as the method
' it will call asynchronously.
Public Delegate Function AsyncMethodCaller(ByVal callDuration As Integer, _
<Out> ByRef threadId As Integer) As String
End Namespace
C# 复制代码
using System;
using System.Threading;

namespace Examples.AdvancedProgramming.AsynchronousOperations
{
public class AsyncDemo
{
// The method to be executed asynchronously.
public string TestMethod(int callDuration, out int threadId)
{
Console.WriteLine("Test method begins.");
Thread.Sleep(callDuration);
threadId = Thread.CurrentThread.ManagedThreadId;
return String.Format("My call time was {0}.", callDuration.ToString());
}
}
// The delegate must have the same signature as the method
// it will call asynchronously.
public delegate string AsyncMethodCaller(int callDuration, out int threadId);
}
使用 EndInvoke 等待异步调用
异步执行方法最简单的方式是通过调用委托的 BeginInvoke 方法来开始执行方法,在主线程上执行一些工作,然后调用委托的 EndInvoke 方法。EndInvoke 可能会阻止调用线程,因为它直到异步调用完成之后才返回。这种技术非常适合文件或网络操作,但是由于 EndInvoke 会阻止它,所以不要从服务于用户界面的线程中调用它。
Visual Basic 复制代码
Imports System
Imports System.Threading
Imports System.Runtime.InteropServices

Namespace Examples.AdvancedProgramming.AsynchronousOperations
Public Class AsyncMain
Shared Sub Main()
' The asynchronous method puts the thread id here.
Dim threadId As Integer

' Create an instance of the test class.
Dim ad As New AsyncDemo()

' Create the delegate.
Dim caller As New AsyncMethodCaller(AddressOf ad.TestMethod)

' Initiate the asynchronous call.
Dim result As IAsyncResult = caller.BeginInvoke(3000, _
threadId, Nothing, Nothing)

Thread.Sleep(0)
Console.WriteLine("Main thread {0} does some work.", _
Thread.CurrentThread.ManagedThreadId)

' Call EndInvoke to Wait for the asynchronous call to complete,
' and to retrieve the results.
Dim returnValue As String = caller.EndInvoke(threadId, result)

Console.WriteLine("The call executed on thread {0}, with return value ""{1}"".", _
threadId, returnValue)
End Sub
End Class

End Namespace
C# 复制代码
using System;
using System.Threading;

namespace Examples.AdvancedProgramming.AsynchronousOperations
{
public class AsyncMain
{
public static void Main()
{
// The asynchronous method puts the thread id here.
int threadId;

// Create an instance of the test class.
AsyncDemo ad = new AsyncDemo();

// Create the delegate.
AsyncMethodCaller caller = new AsyncMethodCaller(ad.TestMethod);

// Initiate the asychronous call.
IAsyncResult result = caller.BeginInvoke(3000,
out threadId, null, null);

Thread.Sleep(0);
Console.WriteLine("Main thread {0} does some work.",
Thread.CurrentThread.ManagedThreadId);

// Call EndInvoke to wait for the asynchronous call to complete,
// and to retrieve the results.
string returnValue = caller.EndInvoke(out threadId, result);

Console.WriteLine("The call executed on thread {0}, with return value /"{1}/".",
threadId, returnValue);
}
}
}
使用 WaitHandle 等待异步调用
您可以使用 BeginInvoke 返回的 IAsyncResult 的 AsyncWaitHandle 属性来获取 WaitHandle。异步调用完成时会发出 WaitHandle 信号,而您可以通过调用 WaitOne 方法等待它。
如果您使用 WaitHandle,则在异步调用完成之前或之后,在通过调用 EndInvoke 检索结果之前,还可以执行其他处理。
Visual Basic 复制代码
Imports System
Imports System.Threading
Imports System.Runtime.InteropServices

Namespace Examples.AdvancedProgramming.AsynchronousOperations

Public Class AsyncMain
Shared Sub Main()
' The asynchronous method puts the thread id here.
Dim threadId As Integer

' Create an instance of the test class.
Dim ad As New AsyncDemo()

' Create the delegate.
Dim caller As New AsyncMethodCaller(AddressOf ad.TestMethod)

' Initiate the asynchronous call.
Dim result As IAsyncResult = caller.BeginInvoke(3000, _
threadId, Nothing, Nothing)

Thread.Sleep(0)
Console.WriteLine("Main thread {0} does some work.", _
Thread.CurrentThread.ManagedThreadId)
' Perform additional processing here and then
' wait for the WaitHandle to be signaled.
result.AsyncWaitHandle.WaitOne()

' Call EndInvoke to retrieve the results.
Dim returnValue As String = caller.EndInvoke(threadId, result)

Console.WriteLine("The call executed on thread {0}, with return value ""{1}"".", _
threadId, returnValue)
End Sub
End Class
End Namespace
C# 复制代码
using System;
using System.Threading;

namespace Examples.AdvancedProgramming.AsynchronousOperations
{
public class AsyncMain
{
static void Main()
{
// The asynchronous method puts the thread id here.
int threadId;

// Create an instance of the test class.
AsyncDemo ad = new AsyncDemo();

// Create the delegate.
AsyncMethodCaller caller = new AsyncMethodCaller(ad.TestMethod);

// Initiate the asychronous call.
IAsyncResult result = caller.BeginInvoke(3000,
out threadId, null, null);

Thread.Sleep(0);
Console.WriteLine("Main thread {0} does some work.",
Thread.CurrentThread.ManagedThreadId);

// Wait for the WaitHandle to become signaled.
result.AsyncWaitHandle.WaitOne();

// Perform additional processing here.
// Call EndInvoke to retrieve the results.
string returnValue = caller.EndInvoke(out threadId, result);

Console.WriteLine("The call executed on thread {0}, with return value /"{1}/".",
threadId, returnValue);
}
}
}
轮询异步调用完成
您可以使用由 BeginInvoke 返回的 IAsyncResult 的 IsCompleted 属性来发现异步调用何时完成。从用户界面的服务线程中进行异步调用时可以执行此操作。轮询完成允许调用线程在异步调用在 ThreadPool 线程上执行时继续执行。
Visual Basic 复制代码
Imports System
Imports System.Threading
Imports System.Runtime.InteropServices

Namespace Examples.AdvancedProgramming.AsynchronousOperations

Public Class AsyncMain
Shared Sub Main()
' The asynchronous method puts the thread id here.
Dim threadId As Integer

' Create an instance of the test class.
Dim ad As New AsyncDemo()

' Create the delegate.
Dim caller As New AsyncMethodCaller(AddressOf ad.TestMethod)

' Initiate the asynchronous call.
Dim result As IAsyncResult = caller.BeginInvoke(3000, _
threadId, Nothing, Nothing)

' Poll while simulating work.
While result.IsCompleted = False
Thread.Sleep(10)
End While

' Call EndInvoke to retrieve the results.
Dim returnValue As String = caller.EndInvoke(threadId, result)

Console.WriteLine("The call executed on thread {0}, with return value ""{1}"".", _
threadId, returnValue)
End Sub
End Class
End Namespace
C# 复制代码
using System;
using System.Threading;

namespace Examples.AdvancedProgramming.AsynchronousOperations
{
public class AsyncMain
{
static void Main() {
// The asynchronous method puts the thread id here.
int threadId;

// Create an instance of the test class.
AsyncDemo ad = new AsyncDemo();

// Create the delegate.
AsyncMethodCaller caller = new AsyncMethodCaller(ad.TestMethod);

// Initiate the asychronous call.
IAsyncResult result = caller.BeginInvoke(3000,
out threadId, null, null);

// Poll while simulating work.
while(result.IsCompleted == false) {
Thread.Sleep(10);
}

// Call EndInvoke to retrieve the results.
string returnValue = caller.EndInvoke(out threadId, result);

Console.WriteLine("The call executed on thread {0}, with return value /"{1}/".",
threadId, returnValue);
}
}
}
异步调用完成时执行回调方法
如果启动异步调用的线程不需要是处理结果的线程,则可以在调用完成时执行回调方法。回调方法在 ThreadPool 线程上执行。
若要使用回调方法,必须将引用回调方法的 AsyncCallback 委托传递给 BeginInvoke。也可以传递包含回调方法将要使用的信息的对象。例如,可以传递启动调用时曾使用的委托,以便回调方法能够调用 EndInvoke。
Visual Basic 复制代码
Imports System
Imports System.Threading
Imports System.Runtime.InteropServices

Namespace Examples.AdvancedProgramming.AsynchronousOperations

Public Class AsyncMain
' The asynchronous method puts the thread id here.
Private Shared threadId As Integer

Shared Sub Main()
' Create an instance of the test class.
Dim ad As New AsyncDemo()

' Create the delegate.
Dim caller As New AsyncMethodCaller(AddressOf ad.TestMethod)

' Initiate the asynchronous call.
Dim result As IAsyncResult = caller.BeginInvoke(3000, _
threadId, _
AddressOf CallbackMethod, _
caller)

Console.WriteLine("Press Enter to close application.")
Console.ReadLine()
End Sub

' Callback method must have the same signature as the
' AsyncCallback delegate.
Shared Sub CallbackMethod(ByVal ar As IAsyncResult)
' Retrieve the delegate.
Dim caller As AsyncMethodCaller = CType(ar.AsyncState, AsyncMethodCaller)

' Call EndInvoke to retrieve the results.
Dim returnValue As String = caller.EndInvoke(threadId, ar)

Console.WriteLine("The call executed on thread {0}, with return value ""{1}"".", _
threadId, returnValue)
End Sub
End Class
End Namespace
C# 复制代码
using System;
using System.Threading;

namespace Examples.AdvancedProgramming.AsynchronousOperations
{
public class AsyncMain
{
// Asynchronous method puts the thread id here.
private static int threadId;

static void Main() {
// Create an instance of the test class.
AsyncDemo ad = new AsyncDemo();

// Create the delegate.
AsyncMethodCaller caller = new AsyncMethodCaller(ad.TestMethod);

// Initiate the asychronous call. Include an AsyncCallback
// delegate representing the callback method, and the data
// needed to call EndInvoke.
IAsyncResult result = caller.BeginInvoke(3000,
out threadId,
new AsyncCallback(CallbackMethod),
caller );

Console.WriteLine("Press Enter to close application.");
Console.ReadLine();
}

// Callback method must have the same signature as the
// AsyncCallback delegate.
static void CallbackMethod(IAsyncResult ar)
{
// Retrieve the delegate.
AsyncMethodCaller caller = (AsyncMethodCaller) ar.AsyncState;

// Call EndInvoke to retrieve the results.
string returnValue = caller.EndInvoke(out threadId, ar);

Console.WriteLine("The call executed on thread {0}, with return value /"{1}/".",
threadId, returnValue);
}
}
}
请参见
参考
Delegate
其他资源
异步编程设计模式

ms-help://MS.MSDNQTR.v80.chs/MS.MSDN.v80/MS.VisualStudio.v80.chs/dv_fxadvance/html/c6baed9f-2a25-4728-9a9a-53b7b14840cf.htm
异步编程设计模式
异步操作通常用于执行完成时间可能较长的任务,如打开大文件、连接远程计算机或查询数据库。异步操作在主应用程序线程以外的线程中执行。应用程序调用方法异步执行某个操作时,应用程序可在异步方法执行其任务时继续执行。
.NET Framework 为异步操作提供两种设计模式:
· 使用 IAsyncResult 对象的异步操作。
· 使用事件的异步操作。
IAsyncResult 设计模式允许多种编程模型,但更加复杂不易学习,可提供大多数应用程序都不要求的灵活性。可能的话,类库设计者应使用事件驱动模型实现异步方法。在某些情况下,库设计者还应实现基于 IAsyncResult 的模型。
有关使用事件的异步操作的文档,请参见基于事件的异步模式概述
.NET Framework 的许多方面都支持异步编程功能,这些方面包括:
· 文件 IO、流 IO、套接字 IO。
· 网络。
· 远程处理信道(HTTP、TCP)和代理。
· 使用 ASP.NET 创建的 XML Web services。
· ASP.NET Web 窗体。
· 使用 MessageQueue 类的消息队列。
本节内容(具体内容参见MSDN)
异步编程概述
讨论基于 IAsyncResult 的异步设计模式,此模式提供灵活的处理异步操作的编程模型。
使用 IAsyncResult 调用异步方法
提供代码示例,演示应用程序检测异步操作结束的各种方式。
使用委托进行异步编程
介绍以异步方式调用同步方法。
使用基于事件的异步模式进行多线程编程
介绍基于事件的异步模式,该模式提供使用事件进行跨线程通信的异步编程模型。
请参见
参考
AsyncCallback
IAsyncResult
Delegate
AsyncOperationManager
BackgroundWorker
概念
异步文件 I/O
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: