当程序启动一个异步调用之后,调用者线程必须有一种方法能知道此调用的执行情况,并且在这一调用执行完毕之后,取回执行结果。
可以有以下二种方法:
一、使用轮询
现在我们来改造上一节的示例程序。我们可以在程序执行异步调用的过程中,让计算机每隔一段时间向控制台输出一个小点,告诉用户搜索工作正在进行中,
从而可以大大改善程序的用户友好性。
示例程序:
namespace AsyncCalculateFolderSize2 { class Program { //计算指定文件夹的总容量 private static long CalculateFolderSize(string FolderName) { if (Directory.Exists(FolderName) == false) { throw new DirectoryNotFoundException("文件夹不存在"); } DirectoryInfo RootDir = new DirectoryInfo(FolderName); //获取所有的子文件夹 DirectoryInfo[] ChildDirs = RootDir.GetDirectories(); //获取当前文件夹中的所有文件 FileInfo[] files = RootDir.GetFiles(); long totalSize = 0; //累加每个文件的大小 foreach (FileInfo file in files) { totalSize += file.Length; } //对每个文件夹执行同样的计算过程:累加其下每个文件的大小 //这是通过递归调用实现的 foreach (DirectoryInfo dir in ChildDirs) { totalSize += CalculateFolderSize(dir.FullName); } //返回文件夹的总容量 return totalSize; } //定义一个委托 public delegate long CalculateFolderSizeDelegate(string FolderName); static void Main(string[] args) { //定义一个委托变量引用静态方法CalculateFolderSize CalculateFolderSizeDelegate d = CalculateFolderSize; Console.WriteLine("请输入文件夹名称(例如:C:\\Windows):"); string FolderName = Console.ReadLine(); //通过委托异步调用静态方法CalculateFolderSize IAsyncResult ret = d.BeginInvoke(FolderName, null, null); Console.Write ("正在计算中,请耐心等待"); while (ret.IsCompleted == false) { Console.Write("."); //每隔2秒检查一次 System.Threading.Thread.Sleep(2000); } //阻塞,等到调用完成,取出结果 long size = d.EndInvoke(ret); Console.WriteLine("\n计算完成。文件夹{0}的容量为:{1}字节\n", FolderName, size); Console.ReadKey(); } } }
上面的程序中,在启动异步调用后,定期查询异步调用的状态,如果还没有完成,就输出一个小点。
IAsyncResult接口中有一个IsCompleted字段,可以用于检查异步调用是否完成。
二、使用等待句柄
上面的代码使用轮询 IAsyncResult.IsCompleted属性值的方式不断询问异步调用是否完成,还可以使用 IAsyncResult提供的另一个属性AsyncWaitHandle实现
同样的目的。 AsyncWaitHanle 是一个等待句柄对象,它定义了一系列重载的 WaitOne方法,我们将使用后个如下所示:
public virtual bool WaitOne(int millisecondsTimeout);
当调用以上形式的WaitOne方法时,调用者线程将在由方法参数millisecondsTimeout指定的时间段内等待“等待句柄”对象的状态转为Signaled,此时
WaitOne方法返回true;如果超时,返回false.
示例代码如下:
namespace AsyncCalculateFolderSize3 { class Program { //计算指定文件夹的总容量 private static long CalculateFolderSize(string FolderName) { if (Directory.Exists(FolderName) == false) { throw new DirectoryNotFoundException("文件夹不存在"); } DirectoryInfo RootDir = new DirectoryInfo(FolderName); //获取所有的子文件夹 DirectoryInfo[] ChildDirs = RootDir.GetDirectories(); //获取当前文件夹中的所有文件 FileInfo[] files = RootDir.GetFiles(); long totalSize = 0; //累加每个文件的大小 foreach (FileInfo file in files) { totalSize += file.Length; } //对每个文件夹执行同样的计算过程:累加其下每个文件的大小 //这是通过递归调用实现的 foreach (DirectoryInfo dir in ChildDirs) { totalSize += CalculateFolderSize(dir.FullName); } //返回文件夹的总容量 return totalSize; } //定义一个委托 public delegate long CalculateFolderSizeDelegate(string FolderName); static void Main(string[] args) { //定义一个委托变量引用静态方法CalculateFolderSize CalculateFolderSizeDelegate d = CalculateFolderSize; Console.WriteLine("请输入文件夹名称(例如:C:\\Windows):"); string FolderName = Console.ReadLine(); //通过委托异步调用静态方法CalculateFolderSize IAsyncResult ret = d.BeginInvoke(FolderName, null, null); Console.Write("正在计算中,请耐心等待"); while(!ret.AsyncWaitHandle.WaitOne(2000)) { //等待2秒钟,输出一个“.” Console.Write("."); } //阻塞,等到调用完成,取出结果 long size = d.EndInvoke(ret); Console.WriteLine("\n计算完成。文件夹{0}的容量为:{1}字节\n", FolderName, size); Console.ReadKey(); } } }
三、异步回调
前面两个示例使用轮询的方式不断询问异步调用是否完成,这无疑会在循环等待上浪费不少CPU时间。能不能让异步调用的方法在结束时自动调用一个方法,并在
这个方法中显示处理结果?
使用异步回调可以满足这个要求。
BeginInvoke 方法定义中的最后两个参数是 "AsyncCallback callback" 和 "object asyncState",这两个参数就是用于异步调用的。
以下是示例代码:
namespace AsyncCalculateFolderSize4 { class Program { //计算指定文件夹的总容量 private static long CalculateFolderSize(string FolderName) { if (Directory.Exists(FolderName) == false) { throw new DirectoryNotFoundException("文件夹不存在"); } DirectoryInfo RootDir = new DirectoryInfo(FolderName); //获取所有的子文件夹 DirectoryInfo[] ChildDirs = RootDir.GetDirectories(); //获取当前文件夹中的所有文件 FileInfo[] files = RootDir.GetFiles(); long totalSize = 0; //累加每个文件的大小 foreach (FileInfo file in files) { totalSize += file.Length; } //对每个文件夹执行同样的计算过程:累加其下每个文件的大小 //这是通过递归调用实现的 foreach (DirectoryInfo dir in ChildDirs) { totalSize += CalculateFolderSize(dir.FullName); } //返回文件夹的总容量 return totalSize; } public delegate long CalculateFolderSizeDelegate(string FolderName); private static CalculateFolderSizeDelegate d=CalculateFolderSize; //用于回调的函数 public static void ShowFolderSize(IAsyncResult result) { long size = d.EndInvoke(result); Console.WriteLine("\n文件夹{0}的容量为:{1}字节\n", (String)result.AsyncState, size); } static void Main(string[] args) { string FolderName; while (true) { Console.WriteLine("请输入文件夹名称(例如:C:\\Windows),输入quit结束程序"); FolderName = Console.ReadLine(); if (FolderName == "quit") break; d.BeginInvoke(FolderName, ShowFolderSize, FolderName); } } } }
注意:
1、调用BeginInvoke方法的那句代码。BeginInvoke方法的第2个参数指定当异步调用结束时回调 ShowFolderSize方法,第3个参数asyncState被填入了要计算的文件夹
名字,此值被BeginInvoke方法包装到自动创建的一个 IAsyncResult类型的对象中,并作为方法实参自动传送给回调方法(即本示例中回调方法ShowFolderSize的参数result),
回调方法通过这一实参的AsyncState字段获取其值。
2、回调方法的返回值类型是void,只能有一个 IAsyncResult类型的参数result,并且要在方法体中调用 EndInvoke 方法以取回方法的执行结果,另外,result参数的AsyncState
属性包含了外界传入的参数信息(本例为文件夹名)。
这个程序现在可以连续输入多个文件夹名称,计算机在后台分别计算,完成后就在控制台窗口中输出结果。
这里还有一个注意点:先输入的文件夹(先执行的任务)并不一定先完成,可能后面执行的任务由于工作量小反而先执行完。
这里可以得出一个结论:如果需要将一些额外的信息传送给回调方法,就将其放入 BeginInvoke 方法的第3个参数asyncSate中。注意到
这个参数的类型为object ,所以可以放置任意类型的数据。 (在后面将会说到这个参数,其实类库中大多数异步的方法都有这个参数)!