C# 异步编程基础:BeginInvoke 与 EndInvoke 详解
C# 中的异步编程允许程序在发起耗时操作的同时执行其他任务。本文深入分析基于委托的 BeginInvoke 和 EndInvoke 机制,解释其工作原理、使用场景及回调函数的实现方式,帮助开发者掌握传统异步模式的核心逻辑。
同步与异步的核心区别
理解异步编程的关键在于区分同步方法与异步方法的执行流程:
- 同步方法:调用后会一直等待执行完毕并返回结果后,程序才会继续执行后续代码。在此期间,主线程被阻塞。
- 异步方法:在被调用后立即返回控制权给主程序。后台线程负责完成任务,主程序可以利用这段“空闲”时间处理其他逻辑,直到需要结果时再获取数据。
异步编程概览:.NET Framework 的实现
.NET Framework 允许通过委托(Delegate)以标准模式异步调用任何方法。CLR(公共语言运行库)会自动为委托生成具有特定签名的 BeginInvoke 和 EndInvoke 方法。
核心组件说明
- BeginInvoke:启动异步调用。
- 参数与要异步执行的方法一致,额外包含两个可选参数:
IAsyncResult状态的回调委托(AsyncCallback)和用户定义的state对象。 - 立即返回,不等待异步操作完成。
- 返回
IAsyncResult接口,可用于监控进度或获取状态。
- 参数与要异步执行的方法一致,额外包含两个可选参数:
- EndInvoke:检索异步调用的结果。
- 可在
BeginInvoke之后随时调用。若异步操作未完成,此调用将阻塞直到完成。 - 参数包括原方法的
out和ref参数,以及由BeginInvoke返回的IAsyncResult对象。
- 可在
四种调用策略
在调用 BeginInvoke 后,根据业务需求可选择以下四种策略来获取结果:
第1步:直接阻塞等待(EndInvoke 阻塞)
发起调用后先进行其他操作,随后直接调用 EndInvoke。如果此时异步任务尚未完成,当前线程将被阻塞直至任务结束。这是最简单且最常见的模式,适用于需要确保结果才能继续的逻辑分支。
第2步:使用 WaitHandle 等待
利用 IAsyncResult.AsyncWaitHandle 属性获取一个 WaitHandle 对象(如 ManualResetEvent)。调用其 WaitOne 方法使线程挂起,直到接收到信号(即异步任务完成)。此方法常用于主线程需要精确等待子任务完成的场景。
第3步:轮询检查进度
定期检查 IAsyncResult.IsCompleted 属性。当值为 true 时,说明异步调用已完成,随后调用 EndInvoke。
注意:这种轮询方式会持续消耗 CPU 资源,性能不如前两种等待机制,仅在特定实时性要求不高且无回调支持的旧式场景中使用。
第4步:回调函数处理
将 AsyncCallback 委托传递给 BeginInvoke。当异步任务完成后,CLR 将在 ThreadPool 线程上自动执行该回调方法。在回调内部,可以通过强制转换 IAsyncResult.AsyncState 获取传入的状态对象,并调用 EndInvoke 清理资源和获取返回值。
警告:务必确保在任何异步路径完成后都调用 EndInvoke,以释放内部句柄,防止资源泄漏。
实战案例解析
案例一:基础委托异步调用(无回调)
以下示例演示了如何定义委托,并使用 BeginInvoke 启动异步任务,同时在主线程执行其他同步工作。最终通过 EndInvoke 阻塞等待结果。
using System;
namespace ConsoleApplication1
{
class Class1
{
// 1. 声明委托
public delegate void AsyncEventHandler();
// 模拟耗时操作
void Event1()
{
Console.WriteLine("Event1 Start");
System.Threading.Thread.Sleep(4000); // 模拟4秒延迟
Console.WriteLine("Event1 End");
}
// 同步执行的普通方法
void Event2()
{
Console.WriteLine("Event2 Start");
int i = 1;
while (i < 10)
{
i = i + 1;
Console.WriteLine("Event2 " + i.ToString());
System.Threading.Thread.Sleep(200); // 加快演示速度
}
Console.WriteLine("Event2 End");
}
static void Main(string[] args)
{
long start = DateTime.Now.Ticks;
Class1 c = new Class1();
Console.WriteLine("Ready - Starting Asynchronous Operation");
// 2. 实例化委托
AsyncEventHandler asy = new AsyncEventHandler(c.Event1);
// 3. 启动异步调用,不传递回调,状态为 null
IAsyncResult ia = asy.BeginInvoke(null, null);
// 4. 主线程继续执行同步任务,体现“异步”优势
c.Event2();
// 5. 等待异步操作完成并获取结果(此处阻塞)
// 如果 Event1 已结束,则立即返回;否则等待至结束
asy.EndInvoke(ia);
long end = DateTime.Now.Ticks;
Console.WriteLine("Total Time Ticks: " + (end - start));
Console.ReadLine();
}
}
}
关键点:在主线程执行 Event2 的过程中,Event1 正在后台运行。只有当两者都结束时,程序才退出。这展示了如何通过并行处理缩短整体响应时间。
案例二:WebRequest 异步请求与回调
网络请求是异步编程的典型应用场景。以下代码展示了如何使用 HttpWebRequest 进行异步 GET 请求,并通过回调函数链式处理响应流读取。
第1步:定义状态类传递上下文
由于回调方法无法直接访问局部变量,我们需要一个状态类来保存请求对象、缓冲区等信息。
public class RequestState
{
const int BUFFER_SIZE = 1024;
public StringBuilder RequestData;
public byte[] BufferRead;
public HttpWebRequest Request;
public Stream ResponseStream;
public Decoder StreamDecode = Encoding.UTF8.GetDecoder();
public RequestState()
{
BufferRead = new byte[BUFFER_SIZE];
RequestData = new StringBuilder("");
Request = null;
ResponseStream = null;
}
}
第2步:发起异步请求
主入口创建 HttpWebRequest 并调用 BeginGetResponse,传入回调方法 RespCallback 和初始状态对象。
// 从命令行获取 URI
Uri HttpSite = new Uri(args[0]);
HttpWebRequest wreq = (HttpWebRequest)WebRequest.Create(HttpSite);
RequestState rs = new RequestState();
rs.Request = wreq;
// 发出异步请求,指定回调和资源
IAsyncResult r = wreq.BeginGetResponse(new AsyncCallback(RespCallback), rs);
// 主线程等待,防止程序在回调执行前退出
ManualResetEvent allDone = new ManualResetEvent(false);
// 在实际生产中,应通过回调中的 Set() 唤醒此事件
第3步:回调处理响应(RespCallback)
当服务器响应到达时,CLR 调用 RespCallback。在此处获取 HttpWebResponse 并开始异步读取流。
private static void RespCallback(IAsyncResult ar)
{
RequestState rs = (RequestState)ar.AsyncState;
HttpWebRequest req = rs.Request;
// 获取响应对象
HttpWebResponse resp = (HttpWebResponse)req.EndGetResponse(ar);
Stream responseStream = resp.GetResponseStream();
rs.ResponseStream = responseStream;
// 开始异步读取数据流,再次指定回调
IAsyncResult iarRead = responseStream.BeginRead(
rs.BufferRead, 0, RequestState.BUFFER_SIZE,
new AsyncCallback(ReadCallBack), rs);
}
第4步:循环读取数据(ReadCallBack)
ReadCallBack 负责从流中分批读取数据,直到读取完毕。这种方式避免了大块内存分配,适合处理大文件传输。
private static void ReadCallBack(IAsyncResult asyncResult)
{
RequestState rs = (RequestState)asyncResult.AsyncState;
Stream responseStream = rs.ResponseStream;
// 结束读取并获取实际读取的字节数
int read = responseStream.EndRead(asyncResult);
if (read > 0)
{
Char[] charBuffer = new Char[RequestState.BUFFER_SIZE];
int len = rs.StreamDecode.GetChars(rs.BufferRead, 0, read, charBuffer, 0);
String str = new String(charBuffer, 0, len);
rs.RequestData.Append(str);
// 继续异步读取下一批数据
responseStream.BeginRead(
rs.BufferRead, 0, RequestState.BUFFER_SIZE,
new AsyncCallback(ReadCallBack), rs);
}
else
{
// 读取结束,输出内容
Console.WriteLine(rs.RequestData.ToString());
responseStream.Close();
}
}
总结
C# 中的异步编程并非凭空创造,而是建立在委托机制之上的标准模式。掌握 BeginInvoke / EndInvoke 这对组合,配合 IAsyncResult 和回调函数,即可应对绝大多数非现代 async/await 时代的异步需求。核心要点包括:
- 使用
delegate定义签名,CLR 自动生成异步版本。 - 始终成对使用
Begin和End方法。 - 通过
IAsyncResult管理状态,通过回调或等待句柄协调线程。 - 在处理复杂流程(如网络 IO)时,合理使用状态类封装上下文信息。
以上就是 C# 异步编程中 BeginInvoke 与 EndInvoke 模式的详细内容,更多关于 .NET 并发编程的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













