C# 异步编程基础:BeginInvoke 与 EndInvoke 详解

向北走
向北走
发布时间:2026-10-14 | 3人浏览过
原创

C# 中的异步编程允许程序在发起耗时操作的同时执行其他任务。本文深入分析基于委托的 BeginInvoke 和 EndInvoke 机制,解释其工作原理、使用场景及回调函数的实现方式,帮助开发者掌握传统异步模式的核心逻辑。

同步与异步的核心区别

理解异步编程的关键在于区分同步方法与异步方法的执行流程:

  • 同步方法:调用后会一直等待执行完毕并返回结果后,程序才会继续执行后续代码。在此期间,主线程被阻塞。
  • 异步方法:在被调用后立即返回控制权给主程序。后台线程负责完成任务,主程序可以利用这段“空闲”时间处理其他逻辑,直到需要结果时再获取数据。

异步编程概览:.NET Framework 的实现

.NET Framework 允许通过委托(Delegate)以标准模式异步调用任何方法。CLR(公共语言运行库)会自动为委托生成具有特定签名的 BeginInvoke 和 EndInvoke 方法。

核心组件说明

  1. BeginInvoke:启动异步调用。
    • 参数与要异步执行的方法一致,额外包含两个可选参数:IAsyncResult 状态的回调委托(AsyncCallback)和用户定义的 state 对象。
    • 立即返回,不等待异步操作完成。
    • 返回 IAsyncResult 接口,可用于监控进度或获取状态。
  2. 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 时代的异步需求。核心要点包括:

  1. 使用 delegate 定义签名,CLR 自动生成异步版本。
  2. 始终成对使用 Begin 和 End 方法。
  3. 通过 IAsyncResult 管理状态,通过回调或等待句柄协调线程。
  4. 在处理复杂流程(如网络 IO)时,合理使用状态类封装上下文信息。

以上就是 C# 异步编程中 BeginInvoke 与 EndInvoke 模式的详细内容,更多关于 .NET 并发编程的资料请关注本站其它相关文章!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表