APM(异步编程模型)支持三种聚集技巧:等待直至完成,轮询和方法回调。
下面我们先来看看等待直至完成聚集(wait-until-done)技巧:
我们为了启动一个异步操作,我们可以调用一些BeginXXXfangfa 。所有这些方法都会将请求操作排队,然后返回一个IAsyncResult对象来标识挂起的操作。为了获取操作的结果,我们可以以IAsyncResult对象为参数调用相应的EndXXX方法。根据记录,所有的EndXXX方法都可以接受一个IAsyncResult作为它的一个参数。我们可以看一个EndRead方法:
Int32 EndRead(IAsyncResult asyncResult)
注意:EndRead方法的唯一一个参数就是IAsyncResult。但是,更重要的一点就是EndRead方法的返回值是Int32类型,与Read方法的返回值相同。当EndRead方法返回时,它返回从FileStream对象中所读取的字节的数量。
下面,我们来看一个小例:
public static void Main(string[] args)
{
//打开指示异步I/O操作的文件
FileStream fs = new FileStream(@"C:\Boot.ini", FileMode.Open,FileAccess.Read, FileShare.Read, 1024, FileOptions.Asynchronous);
Byte[] data = new Byte[100];
//为FileStream对象初始化一个异步读操作
IAsyncResult ar =fs.BeginRead(data, 0, data.Length, null, null);
// 在这里可以执行一些代码,看个人需求,稍后会分析为什么
// 挂起该线程直至异步操作结束并获得结果
Int32 bytesRead = fs.EndRead(ar);
fs.Close();
Console.WriteLine("字节数是:{0}", bytesRead);
Console.WriteLine(BitConverter.ToString(data, 0, bytesRead));
Console.ReadLine();
}
我在自己的机器上得到的结果:
字节数是:82
20-0D-0A-43-3A-5C-47-52-4C-44-52-3D-D2-BB-BC-FC-BB-B9-D4-AD-20-47-68-6F-73-74-20-76-31-31-2E-30-20-0D-0A-5B-62-6F-6F-74-20-6C-6F-61-64-65-72-5D-0D-0A-74-69-6D-65-6F-75-74-3D-33-0D-0A-5B-6F-70-65-72-61-74-69-6E-67-20-73-79-73-74-65-6D-73-5D-0D-0A
如代码所示,该程序没有有效地利用APM。该程序在调用一个BeginXXX方法之后立即调用了一个EndXXX方法,这样做有点笨,因为调用线程进入了睡眠状态,在等待操作的完成,如果希望异步执行该操作,可以调用一个Read方法,这样做效率更高。
但是在BeginXXX和EndXXX方法之间放入一些代码,会看到APM的一些价值,因为这些代码可以在读取文件字节的过程中执行。下面的代码对前面的程序进行了实质性的修改。新版本的程序可以同时执行从多个流中读取数据。在这个例子中,使用FileStream对象,但是该代码可以在所有的派生至Stream的对象上工作,因此它可以从多个文件、套接字甚至串口中同时读取字节。为了支持这些功能,我们必须使用自己编写的AsyncStreamRead类,并将指定的参数传递给AsyncStreamRead的构造器。
private static void ReadMultipleFiles(params String[] pathnames)
{
AsyncStreamRead[] asrs = new AsyncStreamRead[pathnames.Length];
for (Int32 n = 0; n < pathnames.Length; n++)
{
Stream stream = newFileStream(pathnames[n], FileMode.Open, FileAccess.Read, FileShare.Read, 1024,FileOptions.Asynchronous);
asrs[n] = new AsyncStreamRead(stream,100);
}
for (Int32 n = 0; n < asrs.Length; n++)
{
Byte[] bytesRead =asrs[n].EndRead();
Console.WriteLine("读取的字节数为:{0}",bytesRead.Length);
Console.WriteLine(BitConverter.ToString(bytesRead));
}
}
private sealed class AsyncStreamRead
{
private Stream m_stream;
private IAsyncResult m_ar;
private Byte[] m_data;
public AsyncStreamRead(Stream stream, Int32 numBytes)
{
m_stream = stream;
m_data = new Byte[numBytes];
//为Stream对象初始化一个异步读操作
m_ar = stream.BeginRead(m_data,0, numBytes, null, null);
}
public Byte[] EndRead()
{
Int32 numBytesRead =m_stream.EndRead(m_ar);
m_stream.Close();
Array.Resize(ref m_data,numBytesRead);
return m_data;
}
}
上述代码同时执行所有的读操作时,将非常的高效,但该代码还存在一些效率低下的地方。在将所有的读请求排队后,ReadMultipleFiles方法进入第二轮循环,在这轮循环中,ReadMultipleFiles方法按照请求生成的次序为每个流调用EndRead方法。这种方式效率不高,因为不同的流需要不同的时间来读取数据。因此,有可能第二个流中的数据会在第一个流中的数据之前被读取完成。理想情况下,如果发生了这种情况,我们希望首先处理第二个流中的数据,然后在第一个流中的数据读取完成时再处理它。APM确实允许我们实现这一点,但没有采用此处讨论的等待直至完成聚集技巧。
我的个人官网: www.shuonar.com