在许多程序中代码是顺序执行的,如果在代码中调用了一个方法,则必须等待此方法所有的代码执行完毕之后,才能回到原来的地方执行下一行代码,这种程序运行方式称为同步
示例程序
class Program
{
static void Main(string[] args)
{
long size;
string foldname;
Console.WriteLine("请输入文件夹名称:");
foldname = Console.ReadLine();
size = CalculateFolderSize(foldname);
Console.WriteLine("文件夹{0}容量为{1}", foldname, size);
Console.ReadKey();
}
public static long CalculateFolderSize(string folderName)
{
if (Directory.Exists(folderName))
{
new Exception("文件夹不存在");
}
DirectoryInfo direRoot = new DirectoryInfo(folderName);
DirectoryInfo[] dire = direRoot.GetDirectories();
FileInfo[] file= direRoot.GetFiles();
long size=0;
foreach (var f in file)
{
size += f.Length;
}
foreach (var d in dire)
{
size += CalculateFolderSize(d.FullName);
}
return size;
}
}
执行结果:
注意 size = CalculateFolderSize(foldname)这句,如果此方法不返回,则第8句不可能执行
如果CalculateFolder方法执行需要一定时间(层次很深,文件很多),则用户在方法返回前看不到任务信息,有可能会以为死机了。
能不能在调用方法之后,不用等待方法执行完成就马上执行下一条语句
要实现这个功能,就必须采用异步编程模式
修改上述代码Main中方法,采用委托实现
public delegate long CalCulateFolderSizeDelegate(string foldername);
static void Main(string[] args)
{
CalCulateFolderSizeDelegate d = CalculateFolderSize;
Console.WriteLine("请输入一个数:");
string foldname = Console.ReadLine();
IAsyncResult ret = d.BeginInvoke(foldname, null, null);
Console.WriteLine("正在计算中,请耐心等待");
long size = d.EndInvoke(ret);
Console.WriteLine("计算完成,文件夹{0}容量为{1}", foldname, size);
Console.ReadKey();
}
执行结果:
注意上面红色处:执行CalculateFolderSize之后,并不会等待其完成,而是马上执行后面语句,输出一句提示信息,"正在计算中,请耐心等待"。
上述代码中IAsyncResult ret = d.BeginInvoke(foldname, null, null);
通过委托对像d的beginInvoke方法间接地调用静态方法CalculateFolderSize,这就是异步调用
异步调用的关键之处在于静态方法CalculateFolderSize不在主线程(即Main所在方法)中执行,而在另一个辅助线程中与主线程代码并行执行,由于存在两个并行执行的线程,所以启动执行静态之后,必须有办法取回其计算结果,EndInvoke方法可完成这一任务,但它需要一些额外的信息,这些信息是BeginInvoke方法启动异步调用时提供的,这就是BeginInvoke方法的返回值ret,一个IAsyncResult类型的对像,此对像将成为EndInvoke方法的参数。
EndInvoke方法在执行时,如果CalcuateFolderSize方法还未返回,它会停在这儿等待。
在上述异步调用示例中,用户只是看到了一条固定的信息: “正在计算中,请耐心等待…..”,就没有下文了,显然交互不不太好,虽然做不到拥有可视化窗体的window应用程序那样丰富,但做一点小的改进还是可以的,我们可以在程序执行异步调用的过程中,让计算机每隔一段时间(比如2秒)向控制台输出一个小点,告诉用户搜索工作正在进行中,从而可以大大改善程序用户的友好性,
public delegate long CalCulateFolderSizeDelegate(string foldername);
static void Main(string[] args)
{
long size = 0;
CalCulateFolderSizeDelegate d = CalculateFolderSize;
Console.WriteLine("输入一个文件名");
string foldname = Console.ReadLine();
IAsyncResult ret = d.BeginInvoke(foldname, null, null);
Console.WriteLine("正在计算中,请耐心等待");
while (ret.IsCompleted == false)
{
Console.Write(".");
System.Threading.Thread.Sleep(2000);
}
size = d.EndInvoke(ret);
Console.WriteLine("计算完成,文件夹{0}容量为{1}", foldname, size);
Console.ReadKey();
}
IsCompleted属性值的方式不断询问异步调用是否完成,还可以使用IAsyncResult提供的一个属性AsyncWaitHandle
现在每隔2秒,判断异步执行是否完成,未完成输出一个小点,用户体验是好了些,但是这无疑会在循环等待上浪费不少cpu时间,能不能让异步调用的方法在结束时自动调用一个方法,并在这个方法中显示处理结果?
这种情况可以使用异上回调,BeginInvoke方法定义中的最后两个参数是AsyncCallback callback和object asyncState这两个参数就是用于异步调用的
再次改进下程序,使之可以连续输入多个文件夹名称,计算机在后台分别计算,完成后就在控制台窗口中输出结果
示例:
public delegate long CalculateFolderSizeDelegate(string foldname);
private static CalculateFolderSizeDelegate d = CalculateFolderSize;
static void Main(string[] args)
{
string foldname = "";
while (true)
{
Console.WriteLine("请输入文件夹名称,输入quit结束程序");
foldname = Console.ReadLine();
if (foldname == "quit")
break;
d.BeginInvoke(foldname, ShowFolderSize, foldname);
}
}
public static void ShowFolderSize(IAsyncResult result)
{
long size = d.EndInvoke(result);
Console.WriteLine("计算完成,文件夹{0}容量为{1}",result.AsyncState.ToString(), size);
}
执行结果:
先执行的任务并不一定先完成,因为如果后执行的任务工作量小,反而先执行完毕
注意这句 d.BeginInvoke(foldname, ShowFolderSize, foldname)
BeginInvoke方法的第2个参数指定当异步调用结束时回调ShowFolderSize方法,第3个参数asyncState被填入了要计算的文件夹名字,此值被BeginInvoke方法包装到自动创建的一个IAsyncResult类型的对像中,并作为方法实参自动传送给回调方法,回调方法通过这一实参的AsyncState字段获取其值。
同步模式下的异常处理方法,就是在调用此方法的代码处用try和catch处理异常,由于发生异常的代码与调用代码位于同一线程中,因些当异常发生时,计算机会中断当前线程的执行流程,转去执行异常处理代码。
但在异步调用的过程中有异常是如何处理呢?
当一个异步的方法抛出一个异常时,CRL会捕获它,当启动异步调用线程调用EndInvoke方法等待异步调用结束时,CLR会将此异常再次抛出,这样调用者线程即可捕获它。
如果在调用BeginInvoke方法启动异步调用时提供了一个回调方法,则CLR会在捕获方法抛出的异常之后马上调用被回调的方法,而回调方法通常都需要调用EndInvoke方法
总之在EndInvoke方法所在的代码处即可捕获异步调用的异常
示例:
public static void ShowFolderSize(IAsyncResult result)
{
try
{
long size = d.EndInvoke(result);
Console.WriteLine("文件夹{0}的容量为{1}字节\n", (String)result.AsyncState, size);
}
catch (DirectoryNotFoundException e)
{
Console.WriteLine(e.ToString());
}
}
在.Net基类库中,有一些现在的组件直接实现了IAsyncResult异步调用设计模式,这些组件通常同时提供某个方法的同步与异步调用形式
System.Net命名空间中WebRequest,下图展示了其中的方法,
仔细分析会发现有一些规律
1.有一个 Begin方法都有一个对应的End方法
2.每组Begin/End方法都有一个对应的同步方法 如:BeginGetResponse/EndGetResponse GetResponse()
3.End方法与对应的同步方法返回值类型相同
Begin方法返回一个IAsyncResult对像,而End方法的参数接收此对像,这种模式与基于委托的异步调用模式几乎一样
示例代码见下方:实现功能,采用异步回调的方法通知用户文件下载完毕
class Program
{
static void Main(string[] args)
{
string InputUrl = "";
string FileName = "";
Console.WriteLine("输入URL启动异步下载文件任务");
do
{
Console.WriteLine("输入WEB文件");
InputUrl = Console.ReadLine();
if (string.IsNullOrEmpty(InputUrl))
{
Console.WriteLine("URL不能输入空字符串");
continue;
}
Console.WriteLine("输入保存文件名");
FileName = Console.ReadLine();
if (string.IsNullOrEmpty(FileName))
{
Console.WriteLine("文件名不能输入空");
continue;
}
if (InputUrl == "quit" || FileName == "quit")
{
break;
}
try
{
Uri webUri = new Uri(InputUrl);
WebRequest webrequest = WebRequest.Create(webUri);
DownLoadTask d = new DownLoadTask { WebRequestObj = webrequest, SaveFileName = FileName };
Console.WriteLine("{0}已在后台启动下载保存为{1}",InputUrl, FileName);
webrequest.BeginGetResponse(DownLoadFinished, d);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
} while (true);
}
static void DownLoadFinished(IAsyncResult obj)
{
DownLoadTask d = obj.AsyncState as DownLoadTask;
WebResponse webresponse = d.WebRequestObj.EndGetResponse(obj);
string filecontent="";
using(StreamReader reader=new StreamReader (webresponse.GetResponseStream(),Encoding.GetEncoding("gb2312")))
{
filecontent = reader.ReadToEnd();
}
using (StreamWriter write = new StreamWriter(new FileStream(d.SaveFileName, FileMode.Create), Encoding.GetEncoding("gb2312")))
{
write.Write(filecontent);
}
MessageBox.Show(string.Format("{0}下载完成", d.SaveFileName));
}
}
public class DownLoadTask
{
public WebRequest WebRequestObj { get; set; }
public string SaveFileName { get; set; }
}