异步操作(三)
APM的轮询聚焦技巧
就从字面意思来理解,每隔一段时间来查询,异步操作的结果。而怎么实现轮询的方法了,这里就要谈到IAsyncResult接口。它定义了若干个只读属性
public interface IAsyncResult
{
Object AsyncState {get;}
WaitHandle AsyncWaitHandle {get;}
Boolean IsCompleted {get;}
Boolean CompletedSynchronously {get;}
}
其中最常用的属性是AsyncState。使用轮询聚集技巧时,要使用AsyncWaitHandle和IsCompleted属性。而对于CompletedSynchronously属性,有时侯供实现BeginXxx和EndXxx方法的开发人员查询。其实轮询的效率不高,编写轮询聚集技巧的代码时,要让一个线程定期地询问CLR来查看异步请求是否已经完成运行。实质上是浪费了一个线程的时间。
下面是一个轮询聚集技巧的示例:
public static void PollingWithIsCompleted()
{
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);
//每隔10秒查询一次是否操作已经完成
while(!ar.IsCompleted)
{
Console.WriteLine(“Operation not completed;still waiting.”);
Thread.Sleep(10);
}
//获取结果。注意:EndRead方法不能挂起这个线程 因为前面那个循环已经确定操作完成了
Int32 bytesRead=fs.EndRead(ar);
fs.Close();
//显示结果
….
}
上面是通过IAysncResult的IsCompleted属性来判断一个操作是否完成,下面是通过AsyncWaitHandle属性,它返回一个派生自WaitHandle的对象的引用,该对象通常为System.Threading.ManualResetEnent.
public static void PollingWithAsyncWaitHandle()
{
//打开指示异步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);
//与上面例子效果相同
While(!ar.AsyncWaitHandle.WaitOne(10,false)
{
Console.WriteLine(“Operation not completed;still waiting.”);
}
//获取结果。注意:EndRead方法不能挂起这个线程 因为前面那个循环已经确定操作完成了
Int32 bytesRead=fs.EndRead(ar);
fs.Close();
//显示结果
….
}
APM方法回调聚集技巧
在所有ARM聚集技巧中,构建性能和扩展的应用程序框架时,方法回调聚集技巧最好用。该技巧不会将一个线程置于等待状态,而且该技巧还不会定期地检查异步操作是否完成而浪费CPU时间。
首先将异步I/O请求排队等候,然后线程继续执行它希望执行的任何事情。接着当 I/O请求完成时,Windows将工作项加入CLR的线程池的队列中。最后,线程池中的线程将工作项从队列中取出,并调用我们编写的一些方法(通过这种方式我们可以知道异步I/O操作已经完成)。现在回调内部,我们首先调用EndXxx方法来获得异步操作的结果,然后就可以自由的继续处理结果。当回调方法返回时,线程池中的线程返回到线程池中准备服务下一个排队的工作项。
public static class Program
{
//将数组声明为static,以便Main方法和ReadIsDone方法可访问它
private static Byte[] s_data=new Byte[100];
public static void Main()
{
Console.WriteLine(“Main thread ID={0}”,Thread.CurrentThread.ManagedThreadId);
//打开指示异步I/O操作的文件
FileStream fs=new FileStream(@”C:\Boot.ini”,FileMode.Open,FileAccess.Read,
FileShare.Read,1024,FileOptions.Asynchronous);
//为FileStream对象初始化一个异步读操作,并将FileStream对象fs传递给回调方法ReadISDone
fs.BeginRead(s_data,0,s_data.Length,ReadIsDone,fs);
//在这里执行一些其他代码将非常有用…..
//出于演示目的,将主线程挂起
Console.ReadLine();
}
private static void ReadIsDone(IAsyncResult ar)
{
//显示正在执行ReadIsDone方法线程的ID
Console.WriteLine(“ReadIsDone thread Id={0}”,
Thread.CurrentThread.ManagedThreadID
//从IAsyncResult对象中提取FileStream对象
FileStream fs=(FileStream)ar.AsyncState;
//获取结果
Int32 bytesRead=fs.EndRead(ar);
//已经没有操作执行任务,关闭文件
fs.Close();
//显示结果
}
}
因为这里ReadIsDone函数,要通过全局变量才能访问到读取到的数据。都写到Main函数当中去。还有就是要将回调方法名称传递给BeginRead函数以及FileStream对象实例也要传递给该函数(目的通过这种方式将FileStream对象实例传递到了回调方法中)。当然这里可以利用C#匿名方法特征,就不存在这个问题了。
public static void Main()
{
Console.WriteLine(“Main thread ID={0}”,Thread.CurrentThread.ManagedThreadId);
//打开指示异步I/O操作的文件
FileStream fs=new FileStream(@”C:\Boot.ini”,FileMode.Open,FileAccess.Read,
FileShare.Read,1024,FileOptions.Asynchronous);
Byte[] data=new Byte[100];
//为FileStream对象初始化一个异步读操作,并将FileStream对象fs传递给回调方法ReadISDone
fs.BeginRead(s_data,0,s_data.Length,delegate(IAsyncResult ar)
{
//显示正在执行ReadIsDone方法线程的ID
Console.WriteLine(“ReadIsDone thread Id={0}”,
Thread.CurrentThread.ManagedThreadID
//获取结果
Int32 bytesRead=fs.EndRead(ar);
//已经没有操作执行任务,关闭文件
fs.Close();
//显示结果
},null);
//出于演示目的,将主线程挂起
Console.ReadLine();
}
还记得异步操作(二)中最后一个多流读取文件的例子吗,这里改为回调聚集技巧,那么程序效率又得到进一步提高
private static void ReadMultipleFiles(params String[] pathnames)
{
for(Int32 n=0;n<pathname.Length;n++)
{
//打开指示异步I/O操作文件
Sream stream=new FileStream(pathnames[n],FileMode.Open,FileAccess.Read,
FileShare.Read,1024,FileOptions.Asynchronous);
//为Stream 对象初始化一个异步操作
new AsyncStreamRead(stream,100,delegate(Byte[] data
{
//处理数据
});
}
//所有的流都已经打开了,而且所有的读请求都已经排队;他们同时并发执行,
//而且他们结束时它们将被处理
//主线程可以在这里做一些其他工作,如果愿意的话
//处于演示目的,将主线程挂起来
Console.ReadLine();
}
}
//定义一个委托
private delegate void StreamBytesRead(Byte[] streamData);
private sealed class AsyncSreamRead
{
private Stream m_stream;
private Byte[] m_data;
StreamBytesRead m_callback;
public AsyncStreamRead(Stream stream,Int32 numBytes,StreamByteRead callback)
{
m_stream=stream;
m_data=new Byte[numBytes];
m_callback=callback;
stream.BeginRead(m_data,0,numBytes,ReadIsDone,null);
}
//当IO操作结束时调用下述方法
private void ReadIsDone(IAsyncResult ar)
{
Int32 numByteRead=m_stream.EndRead(ar);
m_stream.Close();
//调整数据大小以节省空间
Array.Resize(ref m_data,numBytesRead);
//调用应用程序的回调方法(注意这里调用的方法是个匿名方法 )
m_callback(m_data);
}
}
使用APM执行受计算限制的异步操作
先看一个简单的程序
private static UInt64 Sum(UInt64 n)
{
UInt64 sum=0;
for(UInt64 i=1;i<=n;i++)
{
//检查是否益处,是就抛出一个异常
Checked
{
sum+=I;
}
}
return sum;
}
如果这里n非常大,Sum方法将需要很长时间来执行。那么这里也用异步操作来完成
internal delegate UInt64 SumDelegate(UInt64 n);
这里定义委托,据委托揭秘可知,在编译器里面实际的内容
internal sealed class SumDelegate:MulticastDelegate
{
public SumDelegate(Object object,IntPtr method);
public UInt64 Invoke(UInt64 n);
pulbic IAsyncResult BeginInvoke(UInt64 n,AsyncCallback Callback,Object object);
public UInt64 EndInvoke(IAsyncResult result);
}
相信看到这段代码,就应该会写后面的程序了:
public static void Main()
{
SumDelegate sumDelegate=Sum;
//调用线程池线程
sumDelegate.BeginInvoke(100000000,SumIsDone,sumDelegate);
Console.ReadLine();
}
这里说明一下委托的BeginInvoke方法通过内部调用ThreadPool的QueueUserWorkItem将受限制的操作加入到CLR线程池的队列中。最后,BeginInvoke方法将IAsyncResult对象返回到它的调用者。调用者在执行异步I/O操作时就可以使用这个IAsyncResult对象了。因为BeginInvoke方法将操作加入线程池的队列,所以线程池中的线程将被唤醒,将工作项从队列中取出,然后调用受计算限制的操作。线程池中的线程从执行方法的过称中返回时,线程就返回到线程池。但是本例方法中有SumIsDone,那么该线程就会调用SumIsDone方法。即完成回调。
private static void SumIsDone(IAsyncResult)
{
//从IAsyncResult对象中提取SumDelegate对象
SumDelegate sumDelegate=(SumDelegate)ar.AsyncState;
//获取结果
UInt64 sum=sumDelegate.EndInvoke(ar);
//显示结果
Console.WriteLine(“Sum={0}”,sum);
}
本文知识来源:《CLR Via C#》