.net基础---异步编程
1.概念:
异步编程是建立在委托基础之上的一种编程方法,异步调用的每个方法都在独立的线程中执行,因此,其本质上就是一种多线程程序,可以看成是应用了"简化版本"的多线程技术
2.程序的同步执行和异步执行:
(1)同步编程方式:
程序中代码是按顺序执行的,如果在代码中调用一个函数,则必须等待函数的所有代码执行完毕之后,才能回到原来的地方执行下一行代码,这种程序运行方式成为"同步"
实例:做一个读取文件夹容量的程序
1 static void Main(string[] args) 2 { 3 4 Console.WriteLine("请输入文件夹名称:"); 5 string folderName = Console.ReadLine(); 6 long totalSize= CalcFolderSize(folderName); 7 Console.WriteLine("正在读取,请稍后..."); 8 Console.WriteLine("文件夹容量为:{0}",totalSize); 9 Console.ReadKey(); 10 } 11 private static long CalcFolderSize(string folderName) 12 { 13 try 14 { 15 if (!Directory.Exists(folderName)) 16 return 0; 17 DirectoryInfo rootDir = new DirectoryInfo(folderName); 18 DirectoryInfo[] childDirs = rootDir.GetDirectories(); 19 FileInfo[] files = rootDir.GetFiles(); 20 long totalSize = 0; 21 foreach (FileInfo file in files) 22 { 23 totalSize += file.Length; 24 } 25 foreach (DirectoryInfo dir in childDirs) 26 { 27 totalSize += CalcFolderSize(dir.FullName); 28 } 29 return totalSize; 30 } 31 catch (Exception ex) 32 { 33 Console.WriteLine(ex.Message); 34 return 0; 35 } 36 }
程序运行到第6行,如果函数不返回结果,第7行是不能执行的,如果CalcFolderSize这个函数中读取的文件夹嵌套的很多,那么这个函数的执行时间就会很长,给用户的感觉就像是程序卡死了,而且第7行的友好提示也显示不出来。如果想不用等待函数执行完就执行下一行代码,就必须采用异步编程。
(2)异步编程模式
我们改造上面的代码,执行函数无需等待就能执行下一行的友好提示信息:
1 delegate long CalcFolderSizeHandler(string folderName); 2 static void Main(string[] args) 3 { 4 Console.WriteLine("请输入文件夹名称:"); 5 string folderName = Console.ReadLine(); 6 CalcFolderSizeHandler handler = new CalcFolderSizeHandler(CalcFolderSize); 7 IAsyncResult result = handler.BeginInvoke(folderName, null, null); 8 Console.WriteLine("正在读取,请稍后..."); 9 long totalSize = handler.EndInvoke(result); 10 Console.WriteLine(string.Format("文件夹容量为:{0}", totalSize)); 11 Console.ReadKey(); 12 } 13 private static long CalcFolderSize(string folderName) 14 { 15 try 16 { 17 if (!Directory.Exists(folderName)) 18 return 0; 19 DirectoryInfo rootDir = new DirectoryInfo(folderName); 20 DirectoryInfo[] childDirs = rootDir.GetDirectories(); 21 FileInfo[] files = rootDir.GetFiles(); 22 long totalSize = 0; 23 foreach (FileInfo file in files) 24 { 25 totalSize += file.Length; 26 } 27 foreach (DirectoryInfo dir in childDirs) 28 { 29 totalSize += CalcFolderSize(dir.FullName); 30 } 31 return totalSize; 32 } 33 catch (Exception ex) 34 { 35 Console.WriteLine(ex.Message); 36 return 0; 37 } 38 }
注意上面加红的代码。
异步调用的关键在于函数CalcFolderSize不是在主线程(Main函数所在的线程)中执行的,而是在另一个辅助线程中与主线程并行执行的,由于存在两个并行执行的线程,所以执行函数CalcFolderSize之后,不会等待其完成,而是马上执行下一行代码,输出友好提示信息。
方法启动之后,必须由办法取回计算结果,EndInvoke()方法可以完成这一任务,但他需要一些额外的信息,这些信息是BeginInvoke()方法启动一个异步调用时提供的IAsynResult类型的对象。
如果CalcFolderSize没有返回,他就会停在EndInvoke()处等待。
3.异步调用模式揭秘
(1)BeginInvoke()方法:
public IAsyncResult BeginInvoke( <输出和输入变量>, AsyncCallback callback, object asyncState )
参数的第一部分是定义委托时确定的方法签名中的参数列表(可能不止一个),下面是msdn上的例子
public delegate string AsyncMethodCaller(int callDuration, out int threadId); public string TestMethod(int callDuration, out int threadId) { Console.WriteLine("Test method begins."); Thread.Sleep(callDuration); threadId = Thread.CurrentThread.ManagedThreadId; return String.Format("My call time was {0}.", callDuration.ToString()); } //调用 // Create the delegate. AsyncMethodCaller caller = new AsyncMethodCaller(ad.TestMethod); // Initiate the asychronous call. IAsyncResult result = caller.BeginInvoke(3000, out threadId, null, null); Thread.Sleep(0); Console.WriteLine("Main thread {0} does some work.", Thread.CurrentThread.ManagedThreadId); // Wait for the WaitHandle to become signaled. result.AsyncWaitHandle.WaitOne(); // Perform additional processing here. // Call EndInvoke to retrieve the results. string returnValue = caller.EndInvoke(out threadId, result);
第二部分参数callback是当异步调用结束时自动回调的函数,第三个函数asyncstate用于向第二个参数所确定的callback回调函数提供额外的信息,比如,如果回调函数需要参数,则asyncState可用于提供这些参数,asyncState是object类型的所以他使用比较灵活
(2)EndInvoke方法
public <方法返回值类型> EndInvoke( <声明为ref或out的参数>,IAsyncResult result )
BeginInvoke方法负责生成一个IAsyncResult接口类型的对象并填充其字段,这个IAsyncResult对象可以看成一个"望远镜",启动异步调用的调用者线程通过它可以跨越线程边界,远距离的了解正在另一个线程上运行的方法。
借助IAsyncResult对象,EndInvoke方法不断的查询异步调用方法是否执行完毕,这样启动异步调用的调用者线程就可以获取异步调用的方法结果了。正由于EndInvoke方法由一个不断轮训的过程,所以主线程程序在执行到调用EndInvoke方法时会暂停,等待异步调用方法完成,取回结果后再继续运行。
4.异步回调
EndInvoke会阻塞主线程等待结果返回,这样无疑会浪费CPU时间在循环上等待,能不能让异步调用方法在结束时自动调用一个函数处理结果?使用异步回调可以满足这个要求。
修改上面的代码:
1 class Program 2 { 3 delegate long CalcFolderSizeHandler(string folderName); 4 static CalcFolderSizeHandler handler = new CalcFolderSizeHandler(CalcFolderSize); 5 static void Main(string[] args) 6 { 7 while (true) 8 { 9 Console.WriteLine("请输入文件夹名称:"); 10 string folderName = Console.ReadLine(); 11 IAsyncResult result = handler.BeginInvoke(folderName, ShowFolderSize, folderName); 12 Console.ReadKey(); 13 } 14 } 15 private static void ShowFolderSize(IAsyncResult result) 16 { 17 try 18 { 19 long totalSize = handler.EndInvoke(result); 20 //这里处理没有输入完就就显示结果的问题 21 while (Console.CursorLeft != 0) 22 System.Threading.Thread.Sleep(2*1000); 23 Console.WriteLine(string.Format("文件夹:{0},容量为:{1}", result.AsyncState.ToString(), totalSize)); 24 } 25 catch (Exception ex) 26 { 27 Console.WriteLine(ex.Message); 28 } 29 } 30 private static long CalcFolderSize(string folderName) 31 { 32 try 33 { 34 if (!Directory.Exists(folderName)) 35 return 0; 36 DirectoryInfo rootDir = new DirectoryInfo(folderName); 37 DirectoryInfo[] childDirs = rootDir.GetDirectories(); 38 FileInfo[] files = rootDir.GetFiles(); 39 long totalSize = 0; 40 foreach (FileInfo file in files) 41 { 42 totalSize += file.Length; 43 } 44 foreach (DirectoryInfo dir in childDirs) 45 { 46 totalSize += CalcFolderSize(dir.FullName); 47 } 48 return totalSize; 49 } 50 catch (Exception ex) 51 { 52 Console.WriteLine(ex.Message); 53 return 0; 54 } 55 } 56 }
5.异步编程小结:
异步编程比较适合在后台运行比较耗费时间的比较简单的任务,但是要注意这些任务彼此应是独立的,而且这些任务中不应该有代码直接访问可视化控件。
如果要在后台完成的工作必须按照特定的顺序运行,或者必须访问共享资源,则这些任务就不适合采用异步编程方式,而直接采用多线程技术开发。
浙公网安备 33010602011771号