先來說說同步和異步操作之間的主要區別。在同步I/O操作中,方法將一直處於等待狀態,直到I/O操作完成。而在異步I/O操作中,在開始了I/O操作後,程序的方法可以轉移去執行其它的操作,這樣大大提高了程序執行的效率。
由於Windows是一個多任務的操作系統,在同一時刻系統可能會接受到多個I/O操作請求,要求對磁盤文件執行各種操作。如果采用同步方式,那麼每時每刻最多只能有一個I/O操作在進行,而其它的任務都處於等待狀態,系統的利用率將會大為降低。異步I/O操作則較好地解決了這種性能上的問題。
Stream類支持在同一個流中既可以進行同步讀寫,也可以進行異步讀寫。Stream類是一個抽象類,它為我們提供了BeginRead、BeginWrite、EndReader、EndWrite、Read、Write、Seek等成員方法,協同完成對流的讀寫操作。所有這些方法都是虛方法。因此,在我們自己設計Stream類的派生類時,我們在類用於讀寫的成員方法Read和Write中應該重載這些方法,並同時設計它們同步和異步的執行代碼。BeginRead,EndRead,BeginWrite和EndWrite方法默認為我們提供的是異步讀寫操作方式,如果你的派生類的Read和Write方法執行同步操作時,那麼程序提供的效率不會很好。只有當它們執行異步操作時,我們才能有效地提高程序的執行效率。
Stream類還提供了ReadByte和WriteByte方法,用於一次讀寫方式,這時我們就需要編寫自己的方法來拋出一個異常。
下面的代碼是在.NET聯機幫助中提供的一個異步讀寫操作的例子,程序模擬了一個多處理器系統的工作。
程序清單17-8:
using System; using System.IO; using System.Threading; using BenchUtil; public class BulkImageProcAsync{ public const String ImageBaseName="tmpImage-"; public const int numImages=200; public const int numPixels=512*512; //ProcessImage has a simple O(N)loop,and we can vary the number //of times we repeat that loop to make the app more CPU-bound or more IO-bound. public static int processImageRepeats=20; //Threads must decrement NumImagesToFinish,and protect //their access to it via a mutex. public static int NumImagesToFinish=numImages; public static Object NumImagesMutex=new Object[0]; //WaitObject is signalled when all image processing is done. public static Object WaitObject=new Object[0]; internal static PerfTimer Pf=new PerfTimer("Asynchronous Bulk Image Processor"); public class ImageStateObject{ public byte[] pixels; public int imageNum; } public static void MakeImageFiles(){ int sides=(int)Math.Sqrt(numPixels); Console.Write("Making"+numImages+""+sides+"x"+sides+"images... "); byte[] pixels=new byte[numPixels]; for(int i=0;i<numPixels;i++) pixels[i]=(byte)i; for(int i=0;i<numImages;i++){ FileStream fs=new FileStream(ImageBaseName+i+ ".tmp",FileMode.Create,FileAccess.Write,FileShare.None,8192,false); fs.Write(pixes,0,pixels.Length); FlushFileBuffers(fs.GetHandle()); fs.Close(); } Console.WriteLine("Done."); } public static void ReadInImageCallback(IAsyncResult asyncResult){ ImageStateObject state=(ImageStateObject)asyncResult.AsyncState; Console.WriteLine("Image"+state.imageNum+"was read. "+(asyncResult.CompletedSynchronously?"synchronously":"asyncchronously")); Stream stream=(Stream)asyncResult.AsyncObject; int bytesRead=stream.EndRead(asyncResult); if(bytesRead!=numPixels) throw new Exception("In ReadInImageCallback,got wrong number of bytes from the image! got:"+bytesRead); ProcessImages(state.pixels,state.imageNum); stream.Close(); //Now write out the image. //using async IO here probably swamps the threadpool,since //there are blocked threadpool threads on soon-to-be-//spawned //threadpool threads FileStream fs=new FileStream(ImageBaseName+state.imageNum+".done", FileMode.Create,FileAccess.Write,FileShare.None,4096,false); fs.Write(state.pixels,0,numPixels); //IAsyncResult writeResult=fs.BeginWrtie(state.pixels, //0,numPixels,null,null); //fs.EndWrite(writeResult); fs.Close(); //Release memory as soon as possible,especially global state. state.pixels=null; //Record that an image is done now. lock(NumImageMutex){ NumImagesToFinish--; if(NumImagesToFinish==0){ Monitor.Enter(WaitObject); Monitor.Pulse(WaitObject); Monitor.Exit(WaitObject); } } } public static void ProcessImage(byte[] pixels,int imageNum){ //Console.WriteLine("ProcessImage"+imageNum); //Do some CPU-intensive operation on the image. for(int i=0;i<processImageReports;i++) for(int j=0;j<numPixels;j++) pixels[j]+=1; //Console.WriteLine("ProcessImage"+imageNum+"done."); } public static void ProcessImagesInBulk(){ Console.WriteLine("Processing images..."); //int timer=Pf.StartTimer("ProcessImages"); int timer=Pf.StartTimer("Total Time"); NumImagesToFinish=numImages; AsyncCallback readImagesCallback=new AsyncCallback(ReadInImageCallback); for(int i=0;i<numImages;i++){ ImageStateObject state=new ImageStateObject(); state.pixels=new byte[numPixels]; state.imageNum=i; //Because very large items are read only once,the buffer //on the file stream can be very small to save memory. FileStream fs=new FileStream(ImageBaseName+i+".tmp",FileMode.Open, FileAccess.Read,FileShare.Read,1,true); fs.BeginRead(state.pixels,0,numPixels,readImageCallback,state); } //Ensure all image processing is done. //If not,block until all are finished. bool mustBlock=false; lock(NumImagesMutex) if(NumImagesToFinish>0) mustBlock=true; } if(mustBlock){ Console.WriteLine("All worker threads are queued...Blocking until they complete. numLeft:"+NumImagesToFinish); Monitor.Enter(WaitObject); Monitor.Wait(WaitObject); Monitor.Exit(WaitObject); } Pf.StopTimer(timer); Pf.OutputStoppedTime(); } public static void Cleanup(){ for(int i=0;i<numImages;i++){ File.Delete(ImageBaseName+i+".tmp"); File.Delete(ImageBaseName+i+".done"); } } public static void TryToClearDiskCache(){ //Try to force all pending writes to disk,AND to clear the //disk cache of any data. byte[] bytes=new byte[100*(1<<20)]; for(int i=0;i<bytes.Length;i++) bytes[i]=0; bytes=null; GC.Collect(); Thread.Sleep(2000); } public static void Main(String[] args){ Console.WriteLine("Bulk image processing sample application,using asycn IO"); Console.WriteLine("Simulates applying a simple transformation to "+numImages+" \"imgaes\""); Console.WriteLine("ie,Async FileStream & Threadpool benchmark)"); Console.Writeline("Warning - this test requires"+(numPixels*numImages*2)+" bytes of tmp space"); if(args.length==1){ processImageRepeats=Int32.Parse(args[0]); Console.WriteLine("ProcessImage inner loop - "+processImageRepeats); } MakeImageFiles(); TryToClearDiskCache(); ProcessImageInBulk(); Cleanup(); } [dllimport("KERNEL32",SetlastError=true)] static extern void FlushFileBuffers(int handle); } 這裡是采用同步方法實現同樣功能的程序。 程序清單17-9: using System; using System.IO; using System.Threading; using BenchUtil; public class BulkImageProcSync { public const String ImageBaseName="tmpImage-"; public const int numImages=200; public const int numPixels=512*512; //ProcessImage has a simple O(N) loop,and we can vary the number //of times we repeat that loop to make the app more CPU-bound or more IO-bound. public static int processImageRepeats=20; internal static PerfTimer Pf=new PerfTimer("Synchronous Bulk Image Processor"); public static void MakeImageFiles(){ int sides=(int)Math.Sqrt(numPixels); Console.Write("Making"+numImages+""+sides+"x"+sides+"images... "); byte[] pixels=new byte[numPixels]; for(int i=0;i<numPixels;i++) pixels[i]=(byte)i; for(int i=0;i<numImages;i++){ FileStream fs=new FileStream(ImageBaseName+i+".tmp",FileMode.Create, FileAccess.Write,FileShare.None,8192,false); fs.Write(pixels,0,pixels.Length); FlushFileBuffers(fs.GetHandle()); fs.Close(); } Console.WriteLine("Done."); } public static void ProcessImage(byte[] pixels,int imageNum){ //Console.WriteLine("ProcessImage"+imageNum); //Do some CPU-intensive operation on the image for(int i=0;i<processImageReports;i++) for(int j=0;j<numPixels;j++) pixels[j]+=1; //Console.WriteLine("processImage"+imageNum+"//done."); } public static void ProcessImagesInBulk(){ Console.WriteLine("Processing images..."); int timer=Pf.StartTimer("Total Time"); byte[] pixels=new byte[numPixels]; for(int i=0;i<numImages;i++){ FileStream input=new FileStream(ImageBaseName+i+".tmp", FileMode.Open,FileAccess.Read,FileShare.Read,4196,false); input.Read(pixels,0,numPixels); input:Close(); ProcessImage(pixels,i); FileStream output=new FileStream(ImageBaseName+i+ ".done",FileMode.Create,FileAccess.Write,FileShare.None,4196,false); output.Write(pixels,0,numPixels); output.Close(); } Pf.StopTimer(timer); Pf.OutputStoppedTime(); } public static void Cleanup() { for(int i=0;i<numImages;i++){ File.Delete(ImageBaseName+i+".tmp"); File.Delete(ImageBaseName+i+".done"); } } public static void TryToClearDiskCache(){ bute[] bytes=new byte[100*(1<<20)]; for(int i=0;i<bytes.Length;i++) bytes[i]=0; bytes=null; GC.Collect(); Thread.Sleep(2000); } public static void Main(String[] args){ Console.WriteLine("Bulk image processing sample application,using synchronous IO"); Console.WriteLine("Simulates applying a simple transformation to "+numImages+" \"images\""); Console.WriteLine("ie,Sync FileStream benchmark)"); Console.WriteLine("Warning - this test requires"+(numPixels*numImages*2)+" bytes of tmp space"); if(args.Length==1){ processImageRepeats=Int32.Parse(args[0]); Console.WriteLine("ProcessImage inner loop -"+processImageRepeats); } MakeImageFiles(); TryToClearDiskCache(); ProcessImagesInBulk(); Cleanup(); } [dllimport"KERNEL32",SetlastError=true)] static extern void FlushFileBuffers(int handlw); }