public delegate long CalculateFolderSizeDelegate(string FolderName);
通过Reflactor反编译结果如下:
public sealed class CalculateFolderSizeDelegate: MulticastDelegate
{
public CalculateFolderSizeDelegate(Object target , intmethodPtr)
{ …… }
public virtual long invoke(string FolderName)
{ …… }
public virtual IAsyncResult BeginInvoke( string FolderName,
AsyncCallbackcallback , object asyncState)
{ …… }
public virtual long EndInvoke( IAsyncResultresult )
{ …… }
}
由此我们发现,当我们定义一个委托的时候,实际上是定义了一个委托类型,这个类型有invoke、BeginInvoke()、EndInvoke()这样几个成员方法,而这几个成员方法可以实现一步调用机制。我们看看这几个方法格式怎么定义的:
(1)BeginInvoke方法用于启动异步调用
BeginInvoke()的函数声明:
public IAsyncResult BeginInvoke(
<输入和输出变量>,回调函数callback , 附加信息AsyncState)
函数返回值类型:
public interface IAsyncResult
{
object AsyncState{ get;} //如果有回调函数的话该参数用于保存要传递给回调函数的参数值
WaitHandle AsyncWaitHandle{ get;}
bool CompletedSynchronously{ get;}
bool IsCompleted{ get;} //保存方法是否执行结束,我们可以通过该属性的值来判断异步方法是否执行结束
}
1.BeginInvoke返回IasyncResult,可用于监视调用进度。
2.结果对象IAsyncResult是从开始操作返回的,并且可用于获取有关异步开始操作是否已完成的状态。
3.结果对象被传递到结束操作,该操作返回调用的最终返回值。
4.在开始操作中可以提供可选的回调。如果提供回调,在调用结束后,将调用该回调;并且回调中的代码可以调用结束操作。
5.如果需要将一些额外的信息传送给回调函数,就将其放入BeginInvoke()方法的第3个参数asyncState中。注意到这个参数的类型为Object,所以可以放置任意类型的数据。如果有多个信息需要传送给回调函数,可以将所有要传送的信息封状到一个Struct变量,或者干脆再定义一个类,将信息封装到这个类所创建的对象中,再传送给BeginInvoke()方法。
(2)EndInvoke方法用于检索异步调用结果。
方法声明:
public <方法返回值类型>EndInvoke(<声明为ref或out的参数>, IAsyncResult result )
1.result参数由BeginInvoke()方法传回。.NET借此以了解方法调用是否完成。
2.当EndInvoke方法发现异步调用完成时,它取出此异步调用方法的返回值作为其返回值,如果异步调用方法有声明为ref和out的参数,它也负责填充它。
3.在调用BeginInvoke后可随时调用EndInvoke方法,注意:始终在异步调用完成后调用EndInvoke.
4.如果异步调用未完成,EndInvoke将一直阻塞到异步调用完成。
5.EndInvoke的参数包括需要异步执行的方法的out和ref参数以及由BeginInvoke返回的IAsyncResult。
应用实例:
1.使用轮询等待异步调用完成:使用IAsyncResult的IsCompleted属性来判断异步调用是否完成
虽然上面的方法可以很好地实现异步调用,但是当调用EndInvoke方法获得调用结果时,整个程序就象死了一样,依然要等待异步方法执行结束,这样做用户的感觉并不会太 好,因此,我们可以使用 asyncResult来判断异步调用是否完成,并显示一些提示信息。这样做可以增加用户体验。代码如下:

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
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 ("正在计算中,请耐心等待");
//每隔2秒检查一次,输出一个“."
while (ret.IsCompleted == false)
{
Console.Write(".");
System.Threading.Thread.Sleep(200);
}
//阻塞,等到调用完成,取出结果
long size = d.EndInvoke(ret);
Console.WriteLine("\n计算完成!\n文件夹{0}的容量为:{1}字节", FolderName, size);
}
}
}

这样,当程序在执行CalculateFolderSize这个异步方法的时候主线程并不是“假死”,而是每隔0.2毫秒输出一个“.",这就是异步调用的妙处!
这里需要用到BeginInvoke的返回值IAsyncResult的IsCompleted这个属性来判断异步线程是否执行结束。
2. 使用轮询等待异步调用完成:使用IAsyncResult的AsyncWaitHandle.WaitOne

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
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);
}
}
}

WaitOne的第一个参数表示要等待的毫秒数,在指定时间之内,WaitOne方法将一直等待,直到异步调用完成,并发出通知,WaitOne方法才返回true。当等待指定时间之后,异步调用仍未完成,WaitOne方法返回false,如果指定时间为0,表示不等待,如果为-1,表示永远等待,直到异步调用完成。
3.使用异步回调函数

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
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 task = CalculateFolderSize;
//用于回调的函数
public static void ShowFolderSize(IAsyncResult result)
{
long size = task.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;
task.BeginInvoke(FolderName, ShowFolderSize, FolderName);//第一个参数是异步函数的参数,第二个参数是回调函数,第三个参数是回调函数的参数,回调函数会在异步函数执行结束之后被调用。
}
}
}
}

这个例子中通过循环的输入文件夹名称计算文件夹容量,计算的操作放在异步调用函数中,因此我们在输入下一个文件夹名称时不必等待上一个计算结束,异步函数执行完成之后会自动调用回调函数ShowFolderSize进行结果处理。

对于上面最后一个异步回调的例子有一个缺陷,就是当异步调用的函数与主线程都需要访问同一资源时,要注意解决资源共享的问题。如下图:

修改程序如下:

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
namespace AsyncCalculateFolderSize6
{
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 = new CalculateFolderSizeDelegate(CalculateFolderSize);
//用于回调的函数
public static void ShowFolderSize(IAsyncResult result)
{
try
{
long size = d.EndInvoke(result);
while (Console.CursorLeft != 0)//只有用户不输入,且光标位于第一列时,才输出信息。
{
//等待2秒
System.Threading.Thread.Sleep(2000);
}
Console.WriteLine("\n文件夹{0}的容量为:{1}字节\n", (String)result.AsyncState, size);
}
catch (DirectoryNotFoundException e)
{
Console.WriteLine("您输入的文件夹不存在");
}
}
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);
}
}
}
}

【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· 葡萄城 AI 搜索升级:DeepSeek 加持,客户体验更智能
· 什么是nginx的强缓存和协商缓存
· 一文读懂知识蒸馏