.Net学习难点讨论系列13 – 异步操作
能否让一个用户使用一个软件时有好的感受是一个软件能否成功的一个很重要的方面,适当的使用异步操作完成部分功能,能大大增加用户的使用体验。.NET通过委托提供了一个简单易用的异步编程模式,使用这种多线程机制可以这大大简化了我们手工编写代码处理多线程的工作。之前委托章节介绍的委托调用都是同步完成的,下面我们要介绍的就是进行异步委托调用。这有个前提委托对象的方法调用列表中只能有一个方法。这样当我们调用委托对象的BeginInvoke方法时,会在独立的线程上执行委托引用的方法。而原始线程不会等待,可以继续执行,调用BeginInvoke方法会返回一个IAsyncResult对象。通过IAsyncResult的IsCompleted属性可以查看子线程是否执行完成。
我们可以通过三种方式知道原始线程发起的子线程是否已经完成。这是异步方法调用的三种标准模式:
& wait-until-done等待直到结束模式:在发起异步调用后,可选的进行一些其他处理,通过调用EndInvoke中断原始线程,并等待异步方法完成后,原线程再执行。如果调用EndInvoke时,子线程已经完成,则直接返回,并继续执行主线程未完的操作。
& polling轮询模式:原线程定期检查子线程是否完成,如果没有完成,则可以先执行其他操作。
& callback回调模式:原线程无需中断等待或定期检查子线程的完成情况,这种模式下委托引用的方法执行完成后,子线程会调用回调方法,回调方法中调用EndInvoke方法获取并处理异步操作结果。
BeginInvoke的参数除了最后两个外都是委托所需的参数,另外对于每一个BeginInvoke都要确保调用了EndInvoke,以保证资源被释放。EndInvoke接收的参数即BeginInvoke方法的返回值 – IAsyncResult的对象。特别注意如果异步调用的方法(即委托调用的方法)中含有ref或out参数,则调用EndInvoke时需要将ref/out参数放在IAsyncResult参数之前。
最后我们分别给出几种模式的代码示例:
1. wait-until-done等待直到结束模式
原始线程发起一个异步方法的调用,做一些其他处理,然后停止并等待,直到开启的子线程结束。首先是异步调用的委托,后面几种模式的示例代码将以其为基础。
1 delegate long MyDel(int first, int second);
接着是等待直到结束模式的代码:
1 class Program
2 {
3 static long Sum(int x, int y)
4 {
5 //模拟一个较长的工作时间
6 Thread.Sleep(1000);
7 return x + y;
8 }
9
10 static void Main(string[] args)
11 {
12 MyDel del = new MyDel(Sum);
13 //开始异步调用
14 IAsyncResult iar = del.BeginInvoke(3, 5, null, null);
15 Console.WriteLine("进行主线程其它工作");
16 //等待异步操作结束,并获取结果
17 long result = del.EndInvoke(iar);
18 }
19 }
2. polling模式
原始线程发起了异步方法的调用,这期间可以做一些其他处理,定期检查IAsyncResult对象的IsCompleted属性来判断开启的线程是否完成。如果未完成则继续处理主线程的工作,否则调用EndInvoke得到异步处理的结果,示例:
1 static void Main(string[] args) 2 { 3 MyDel del = new MyDel(Sum); 4 //开始异步调用 5 IAsyncResult iar = del.BeginInvoke(3, 5, null, null); 6 while (!iar.IsCompleted) 7 { 8 //模拟主线程任务 9 for (long i = 0; i < 1000000; i++) ; 10 } 11 //异步操作结束,获取结果 12 long result = del.EndInvoke(iar); 13 }
3. callback回调
不同于前两种模式中主线程在开启了子线程后还要等待或查询子线程的状态,回调模式中主线程在开启子线程后将不再考虑子线程而是继续自己的工作。异步线程在完成操作后将调用一个用于自定义的回调函数,这个函数中将调用EndInvoke方法来获得异步处理结果并可选的进行进一步操作。
BeginInvoke方法的最后两个参数与回调有关:
第一个参数,callback,接收一个回调方法(的委托对象)
第二个参数,传入回调方法的参数
回调方法需要符合AsyncCallback委托,如:
1 static void CallWhenDone(IAsyncResult iar) 2 { 3 AsyncResult ar = (AsyncResult) iar; 4 MyDel del = (MyDel) ar.AsyncDelegate; 5 long result = del.EndInvoke(iar); 6 }
有了这个回调函数,我们在调用BeginInvoke时可以传入这样的参数:
1 IAsyncResult iar = del.BeginInvoke(3, 5, new AsyncCallback(CallWhenDone), null);
借助.NET对委托推断的支持,上面的代码可以简写为:
1 IAsyncResult iar = del.BeginInvoke(3, 5, CallWhenDone, null);
另外,第“二”参数,如果没有什么其它用途,我们可以将委托对象(del)通过这个参数传入回调函数中:
1 IAsyncResult iar = del.BeginInvoke(3, 5, CallWhenDone, del);
在回调方法中,我们可以通过IAsyncResult参数的AsyncState属性得到(传入参数即)委托对象的引用。注意AsyncState属性为object类型,需要将其转换为相应的委托类型:
1 static void CallWhenDone(IAsyncResult iar) 2 { 3 MyDel del = (MyDel) iar.AsyncState; 4 long result = del.EndInvoke(iar); 5 }
最终将上面这些代码段连在一起就可以了。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异