异步操作(三)

  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#》

posted @ 2010-05-08 12:14  胡佳180815  阅读(1742)  评论(2编辑  收藏  举报