您的位置:首页 > 编程语言

异步编程模式(三):等待异步调用的完成

2012-01-19 19:50 423 查看
当程序启动一个异步调用之后,调用者线程必须有一种方法能知道此调用的执行情况,并且在这一调用执行完毕之后,取回执行结果。

可以有以下二种方法:

一、使用轮询

现在我们来改造上一节的示例程序。我们可以在程序执行异步调用的过程中,让计算机每隔一段时间向控制台输出一个小点,告诉用户搜索工作正在进行中,

从而可以大大改善程序的用户友好性。

示例程序:

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();
}
}
}


[b]三、异步回调[/b]

前面两个示例使用轮询的方式不断询问异步调用是否完成,这无疑会在循环等待上浪费不少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 ,所以可以放置任意类型的数据。 (在后面将会说到这个参数,其实类库中大多数异步的方法都有这个参数)!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: