在.Net4.5中支持的三种异步模型:
基于事件的异步模式 (Event-based Asynchronous Pattern, EAP)
异步编程模型 (Asynchronous Programming Model, APM)
基于任务的异步模式 (Task-based Asynchronous Pattern, TAP)
EAP特性:
- 有一个或多个 [方法名]Async() 方法
- 可能含有 [方法名]Completed 的事件,用于在异步方法结束时作为回调
- 可能含有 [方法名]AsyncCancel() 的方法,用于取消异步过程
举个例子:在Sysetm.Net.WebClient类中, 有以下成员
string DownloadString(string Address) // 同步下载字符串
void DownloadStringAsync(string Address) // 异步下载字符串
DownloadStringCompleted // 在异步下载完成之后触发的事件
// 通常的做法是调用Async方法, 并且在Completed事件触发后对UI做出响应
// 但是缺点就是可能会为了响应完成事件在类中生成太多方法
APM特性:
- 返回值IAsyncResult
- 有一对方法, Begin[方法名] () 和 End[方法名] ();
- 在调用 Begin[方法名] () 之后任务完成时需要调用 End[方法名] () 获取任务结果
举个例子:典型的System.Net.Sockets.Socket 下的 BeginConnect(), EndConnect() 等
TAP特性:
- 用一个方法表示异步操作和它的结果
在.Net4.5中增加了async 和 await两个关键字的支持
await用于阻塞当前线程,等待调用函数完成
方法内部使用await关键字的话在方法签名中需要加上async
当方法的返回值是Task或者Task<T>时,方法就是可等待的
T val = await Method();
System.Net.Sockets命名空间下的套接字提供了APM的异步模型
本文主要介绍如何将其封装成其它的异步模型
以下代码中均不包含异常处理, 多线程处理, 只做简单演示用
以下代码中均不包含异常处理, 多线程处理, 只做简单演示用
以下代码中均不包含异常处理, 多线程处理, 只做简单演示用
重要的事情说三遍
class MySocket
{
Socket socket = null;
// 构造函数以及socket的初始化在此省略
}
首先是Connect
public Task ConnectAsync(string host, int port)
{
DnsEndPoint endpoint = new DnsEndPoint(host, port);
return Task.Factory.StartNew(()=>
{
// 在新线程中调用套接字的连接
// 可以是异步也可以是同步
// 异步连接需要阻塞创建的新线程以让Task在套接字连接建立之后结束(而不是在调用BeginConnect或者ConnectAsync之后立即结束)
// 异步连接可以使用ManualResetEvent对象阻塞线程
socket.Connect(endpoint);
// 这里开始接收消息
BeginReceive();
return;
});
}
接下来需要接收来自对方的消息
由于接收消息是被动的(需要对方发送消息),因此个人偏好于将消息的接收封装成事件(EAP)
// 在这里为了简化操作委托有三个参数,并不符合事件的设计规范
public delegate void SocketMessageReceivedEventHandler(object sender, byte[] data, int len);
class MySocket
{
public event SocketMessageReceivedEventHandler MessageReceived;
private Task recvTask = null;
private byte[] buffer = new byte[2048];
...
// 在套接字连接建立成功之后调用BeginReceive以准备消息接收
// 虽然是BeginReceive但是它不是APM模型
private void BeginReceive()
{
// 创建一个新任务
// 不能等待它,因为它是一个无限循环
recvTask = Task.Factory.StartNew(()=>
{
while(true)
{
// 套接字的数据接收有两个陷阱:粘包和包分段,请注意
// 这里演示同步接收
// 如果是异步接收, 在回调函数中调用事件并继续接收数据即可
int actualDataLength = socket.Receive(buffer, buffer.Length, SocketFlags.None);
if(MessageReceived != null)
// 再次强调,在这里为了简化操作委托有三个参数,并不符合事件的设计规范
// 如果需要更多信息,请使用自定义类型
MessageReceived(this, buffer, actualDataLength);
}
});
}
}
之后是处理消息的发送
// 这个是同步版本
public Task<int> SendSyncVersionAsync(byte[] data)
{
return Task.Factory.StartNew<int>(()=>
{
return socket.Send(data);
});
}
// 这个是异步版本
// 异步版本中演示了TaskCompletionSource的基本使用方法
public Task<int> SendAsyncVersionAsync(byte[] data)
{
TaskCompletionSource<int> tcs = new TaskCompletionSource<int>();
socket.BeginSend(data, 0, data.Length, SocketFlags.None, SendCallBack, tcs);
return tcs.Task;
}
private void SendCallBack(IAsyncResult iar)
{
TaskCompletionSource<int> tcs = (TaskCompletionSource<int>) iar.AsyncState;
int actuallen = socket.EndSend(iar);
tcs.SetResult(actuallen);
}
测试代码
static async void TrySocket()
{
MySocket socket = new MySocket();
socket.MessageReceived += Socket_MessageReceived;
Console.WriteLine("准备连接");
await socket.ConnectAsync("localhost", 12345);
Console.WriteLine("连接成功");
int sendlen = await socket.SendAsyncVersionAsync(new byte[] { 1, 2, 3 });
Console.WriteLine("发送成功: {0}", sendlen);
}
private static void Socket_MessageReceived(object sender, byte[] data, int len)
{
Console.WriteLine("接收成功: {0}", len);
}
static void Main(string[] args)
{
TrySocket();
while (true) ;
}
输出
准备连接
连接成功
发送成功: 3
接收成功: 13