C#中的BeginInvoke和EndInvoke:异步编程的双剑客

article/2025/9/8 20:54:03

文章目录

    • 引言
    • 1. BeginInvoke和EndInvoke的基本概念
      • 1.1 什么是BeginInvoke和EndInvoke
      • 1.2 重要概念解释
    • 2. 委托中的BeginInvoke和EndInvoke
      • 2.1 BeginInvoke方法
      • 2.2 EndInvoke方法
      • 2.3 两者的关系
    • 3. 使用方式与模式
      • 3.1 等待模式
      • 3.2 轮询模式
      • 3.3 等待句柄模式
      • 3.4 回调模式
    • 4. 底层实现原理
      • 4.1 委托的底层模型
      • 4.2 BeginInvoke的工作原理
      • 4.3 EndInvoke的工作原理
    • 5. 与现代异步编程的比较
      • 5.1 Task-based Asynchronous Pattern (TAP)
      • 5.2 优缺点比较
      • 5.3 使用建议
    • 6. 最佳实践与注意事项
      • 6.1 始终调用EndInvoke
      • 6.2 异常处理
      • 6.3 线程安全性考虑
      • 6.4 避免线程资源耗尽
    • 7. 总结与展望
    • 学习资源

引言

在C#的多线程编程中,BeginInvoke和EndInvoke是两个非常重要的方法,它们为开发者提供了一种简单而强大的异步编程模型。这两个方法允许我们在不阻塞主线程的情况下执行耗时操作,从而提高应用程序的响应性和性能。本文将深入探讨BeginInvoke和EndInvoke的工作原理、使用方法以及它们在现代C#编程中的定位。

1. BeginInvoke和EndInvoke的基本概念

1.1 什么是BeginInvoke和EndInvoke

BeginInvoke和EndInvoke是.NET Framework中提供的一对用于实现异步调用的方法。它们属于异步编程模型(APM),是早期.NET Framework中处理异步操作的标准方式。

  • BeginInvoke:启动异步操作并立即返回,不等待操作完成
  • EndInvoke:获取异步操作的结果,如果操作尚未完成则阻塞直到完成

这两个方法主要存在于两类对象中:

  1. 委托(Delegate):用于异步执行委托方法
  2. Windows窗体控件(Control):用于安全地从工作线程更新UI元素

需要注意的是,这两种情况下的BeginInvoke和EndInvoke功能和用途是不同的。本文将主要关注委托中的BeginInvoke和EndInvoke。

1.2 重要概念解释

在深入了解BeginInvoke和EndInvoke之前,我们需要理解几个重要概念:

  • 同步调用:调用方法时,调用线程会等待方法执行完成才继续执行
  • 异步调用:调用方法后,调用线程立即继续执行,不等待方法执行完成
  • 回调:异步操作完成后执行的方法
  • IAsyncResult:表示异步操作的接口,包含异步操作的状态和结果
主线程 线程池线程 BeginInvoke(启动异步操作) 返回IAsyncResult 继续执行其他操作 执行异步操作 EndInvoke(获取结果) 返回操作结果 主线程 线程池线程

2. 委托中的BeginInvoke和EndInvoke

在C#中,委托是一种类型安全的函数指针,可以引用具有特定参数列表和返回类型的方法。每个委托类型都自动具有BeginInvoke和EndInvoke方法,这些方法由CLR自动生成。

2.1 BeginInvoke方法

BeginInvoke方法启动异步调用,它具有以下特点:

  • 参数与委托方法相同,外加两个可选参数:AsyncCallback回调和object状态对象
  • 立即返回,不等待操作完成
  • 返回IAsyncResult对象,用于跟踪异步操作状态
// BeginInvoke的典型签名
public IAsyncResult BeginInvoke([委托参数列表], AsyncCallback callback,  // 可选的回调函数object state             // 可选的状态对象
);

2.2 EndInvoke方法

EndInvoke方法用于获取异步操作的结果:

  • 参数包括委托方法的输出参数和IAsyncResult对象
  • 如果异步操作未完成,会阻塞调用线程直到操作完成
  • 返回委托方法的返回值
  • 负责释放异步操作使用的资源
// EndInvoke的典型签名
public [返回值类型] EndInvoke([out参数列表],  IAsyncResult result  // BeginInvoke返回的IAsyncResult对象
);

2.3 两者的关系

BeginInvoke和EndInvoke构成了一个完整的异步调用模式:

  • BeginInvoke负责启动异步操作
  • EndInvoke负责获取结果和清理资源
  • 两者之间通过IAsyncResult对象关联

无论使用哪种模式调用BeginInvoke,都必须确保调用EndInvoke,否则可能导致资源泄露。

3. 使用方式与模式

使用BeginInvoke和EndInvoke有四种常见模式:

3.1 等待模式

最简单的模式是先调用BeginInvoke,然后在需要结果时调用EndInvoke:

// 定义一个计算密集型的委托
public delegate int CalculateDelegate(int value);public static void WaitPattern()
{// 创建委托实例CalculateDelegate calculate = new CalculateDelegate(ExpensiveCalculation);Console.WriteLine("开始异步计算...");// 异步调用IAsyncResult result = calculate.BeginInvoke(10, null, null);// 主线程继续执行其他工作Console.WriteLine("主线程继续执行其他工作...");// 在需要结果时调用EndInvoke,如果计算未完成会阻塞int calculationResult = calculate.EndInvoke(result);Console.WriteLine($"计算结果: {calculationResult}");
}// 模拟耗时计算
public static int ExpensiveCalculation(int value)
{// 模拟耗时操作Console.WriteLine("开始执行耗时计算...");Thread.Sleep(3000);Console.WriteLine("计算完成");return value * value;
}

3.2 轮询模式

使用IAsyncResult.IsCompleted属性定期检查异步操作是否完成:

public static void PollPattern()
{CalculateDelegate calculate = new CalculateDelegate(ExpensiveCalculation);// 开始异步计算IAsyncResult result = calculate.BeginInvoke(10, null, null);// 轮询检查操作是否完成while (!result.IsCompleted){// 显示进度或执行其他工作Console.Write(".");Thread.Sleep(200);}// 操作完成,获取结果int calculationResult = calculate.EndInvoke(result);Console.WriteLine($"\n计算结果: {calculationResult}");
}

3.3 等待句柄模式

使用IAsyncResult.AsyncWaitHandle属性获取WaitHandle,然后调用WaitOne方法等待异步操作完成:

public static void WaitHandlePattern()
{CalculateDelegate calculate = new CalculateDelegate(ExpensiveCalculation);// 开始异步计算IAsyncResult result = calculate.BeginInvoke(10, null, null);// 获取等待句柄WaitHandle waitHandle = result.AsyncWaitHandle;// 等待操作完成,最多等待5秒if (waitHandle.WaitOne(5000, false)){// 操作在超时前完成int calculationResult = calculate.EndInvoke(result);Console.WriteLine($"计算结果: {calculationResult}");}else{// 操作超时Console.WriteLine("操作超时!");}// 记得关闭等待句柄waitHandle.Close();
}

3.4 回调模式

使用AsyncCallback委托在异步操作完成时接收通知:

public static void CallbackPattern()
{CalculateDelegate calculate = new CalculateDelegate(ExpensiveCalculation);// 开始异步计算,指定回调方法calculate.BeginInvoke(10, CalculationCompleted, calculate);Console.WriteLine("主线程继续执行,不等待计算完成...");// 防止主线程退出Console.ReadLine();
}// 回调方法
private static void CalculationCompleted(IAsyncResult ar)
{// 从状态对象中获取委托CalculateDelegate calculate = (CalculateDelegate)ar.AsyncState;// 获取计算结果int result = calculate.EndInvoke(ar);Console.WriteLine($"异步回调: 计算结果 = {result}");
}

4. 底层实现原理

BeginInvoke和EndInvoke的底层实现涉及到.NET运行时的多个组件:

4.1 委托的底层模型

在.NET中,委托实际上是一个特殊的类,它继承自System.MulticastDelegate,后者继承自System.Delegate。委托类包含几个重要的字段:

// 简化的委托内部结构
public abstract class Delegate
{// 调用目标对象internal object _target;  // 如果是静态方法则为null// 目标方法的指针internal IntPtr _methodPtr;// 静态方法的指针internal IntPtr _methodPtrAux;
}public abstract class MulticastDelegate : Delegate
{// 多播委托的调用列表private object _invocationList;private int _invocationCount;
}

4.2 BeginInvoke的工作原理

当调用委托的BeginInvoke方法时,以下过程会发生:

  1. CLR创建一个表示异步操作的对象(AsyncResult)
  2. 从线程池中获取一个工作线程
  3. 在工作线程上执行委托方法
  4. 立即向调用者返回AsyncResult对象

BeginInvoke方法不会在调用线程上执行委托方法,而是将执行工作委托给线程池,这样调用线程可以继续执行其他任务。

4.3 EndInvoke的工作原理

当调用EndInvoke方法时:

  1. 如果异步操作未完成,调用线程会阻塞直到操作完成
  2. 获取异步操作的结果或异常
  3. 释放与异步操作相关的资源

EndInvoke的实现使用了WaitHandle确保调用线程在异步操作完成前保持阻塞状态。

调用BeginInvoke
创建AsyncResult对象
从线程池获取工作线程
在工作线程上执行委托
返回AsyncResult
调用EndInvoke
异步操作完成?
阻塞等待
获取结果
释放资源

5. 与现代异步编程的比较

BeginInvoke和EndInvoke是.NET早期的异步编程模型,随着.NET的发展,出现了更现代的异步编程方式:

5.1 Task-based Asynchronous Pattern (TAP)

在现代.NET中,更推荐使用Task和async/await模式:

// 使用Task
public static async Task ModernAsyncExample()
{Console.WriteLine("开始异步操作...");// 使用Task.Run启动异步操作Task<int> calculationTask = Task.Run(() => ExpensiveCalculation(10));// 执行其他工作Console.WriteLine("主线程继续执行其他工作...");// 异步等待结果int result = await calculationTask;Console.WriteLine($"计算结果: {result}");
}

5.2 优缺点比较

BeginInvoke/EndInvoke与Task/async/await的比较:

特性BeginInvoke/EndInvokeTask/async/await
代码复杂度较高较低
可读性一般优秀
组合操作困难简单
错误处理复杂简单,与同步代码类似
取消支持需手动实现内置支持
状态机生成是,编译器生成
.NET版本所有版本.NET 4.5+

5.3 使用建议

  • 新项目:优先使用Task/async/await
  • 维护老项目:可以继续使用BeginInvoke/EndInvoke,或考虑重构
  • 需要兼容较老版本.NET:可能需要使用BeginInvoke/EndInvoke

值得注意的是,在.NET Core和.NET 5+中,委托的BeginInvoke和EndInvoke方法已被标记为过时,但在Windows Forms应用程序中,Control.BeginInvoke和Control.Invoke仍然是跨线程操作UI的推荐方式。

6. 最佳实践与注意事项

6.1 始终调用EndInvoke

无论使用哪种模式,都必须调用EndInvoke以释放资源:

// 错误示例 - 资源泄漏
delegate.BeginInvoke(param1, param2, null, null);
// 没有调用EndInvoke,可能导致资源泄漏// 正确示例
IAsyncResult result = delegate.BeginInvoke(param1, param2, null, null);
delegate.EndInvoke(result);  // 确保调用EndInvoke

6.2 异常处理

异步操作中的异常在EndInvoke调用时抛出:

public static void ExceptionHandlingExample()
{CalculateDelegate calculate = new CalculateDelegate(CalculationWithException);IAsyncResult result = calculate.BeginInvoke(0, null, null);try{// 如果异步操作抛出异常,EndInvoke会重新抛出int calculationResult = calculate.EndInvoke(result);Console.WriteLine($"计算结果: {calculationResult}");}catch (DivideByZeroException ex){Console.WriteLine($"捕获到异常: {ex.Message}");}
}public static int CalculationWithException(int value)
{// 故意抛出异常return 100 / value;  // 当value为0时抛出DivideByZeroException
}

6.3 线程安全性考虑

在异步回调中访问UI元素时,需要确保在UI线程上执行:

// Windows Forms示例
private void AsyncOperationButton_Click(object sender, EventArgs e)
{CalculateDelegate calculate = new CalculateDelegate(ExpensiveCalculation);// 启动异步操作calculate.BeginInvoke(10, (ar) => {// 获取结果int result = calculate.EndInvoke(ar);// 安全地更新UIthis.Invoke(new Action(() => {resultLabel.Text = $"计算结果: {result}";}));}, null);
}

6.4 避免线程资源耗尽

在短时间内创建大量异步操作时要小心,因为每个操作都会消耗线程池资源:

// 潜在问题 - 可能导致线程池资源耗尽
for (int i = 0; i < 1000; i++)
{calculate.BeginInvoke(i, null, null);  // 不推荐
}// 更好的方式 - 控制并发度
int maxConcurrency = Environment.ProcessorCount * 2;
SemaphoreSlim semaphore = new SemaphoreSlim(maxConcurrency);for (int i = 0; i < 1000; i++)
{semaphore.Wait();  // 获取信号量calculate.BeginInvoke(i, (ar) => {try{calculate.EndInvoke(ar);}finally{semaphore.Release();  // 释放信号量}}, null);
}

7. 总结与展望

BeginInvoke和EndInvoke是.NET早期提供的异步编程模型,为开发者提供了在不阻塞主线程的情况下执行耗时操作的能力。尽管在现代.NET开发中,Task和async/await已经成为更推荐的异步编程方式,但理解BeginInvoke和EndInvoke的工作原理和使用方法仍然对以下方面有帮助:

  1. 维护使用这种模式的遗留代码
  2. 深入理解.NET异步编程的演变历程
  3. 在某些特定场景下需要更细粒度控制异步操作

随着.NET的不断发展,异步编程模型也在不断完善。尽管BeginInvoke和EndInvoke在新代码中的应用越来越少,但它们作为.NET异步编程历史的重要组成部分,承载了许多宝贵的设计经验,这些经验已经融入到现代异步编程模型中。

学习资源

  • Microsoft文档:异步编程模型
  • Microsoft文档:使用委托异步调用方法

在这里插入图片描述


http://www.hkcw.cn/article/gHPfQSEkEm.shtml

相关文章

基于通义千问的儿童陪伴学习和成长的智能应用架构。

1.整体架构概览 我们的儿童聊天助手将采用典型的语音交互系统架构,结合大模型能力和外部知识库: 2. 技术方案分解 2.1. 前端应用/设备 选择: 移动App(iOS/Android)、Web应用,或者集成到智能音箱/平板等硬件设备中。技术栈: 移动App: React Native / Flutter (跨平台…

【STIP】安全Transformer推理协议

Secure Transformer Inference Protocol 论文地址&#xff1a;https://arxiv.org/abs/2312.00025 摘要 模型参数和用户数据的安全性对于基于 Transformer 的服务&#xff08;例如 ChatGPT&#xff09;至关重要。虽然最近在安全两方协议方面取得的进步成功地解决了服务 Transf…

MyBatisPlus(1):快速入门

我们知道&#xff0c;MyBatis是一个优秀的操作数据库的持久层框架&#xff08;优秀持久层框架——MyBatis&#xff09;&#xff0c;其基于底层的JDBC进行高度封装&#xff0c;极大的简化了开发。但是对于单表操作而言&#xff0c;我们需要重复地编写简单的CRUD语句。这其实是不…

【ARM】【FPGA】【硬件开发】Chapter.1 AXI4总线协议

Chapter.1 AXI4总线协议 作者&#xff1a;齐花Guyc(CAUC) 一、总线介绍 AXI4总线 AXI4总线就像是SoC内部的“高速公路”&#xff0c;负责在不同硬件模块之间高效传输数据。 AXI4协议通过 5个独立通道 传输数据和控制信号&#xff0c;每个通道都有自己的信号线&#xff0c;互…

.NET 7 AOT 使用及 .NET 与 Go 语言互操作详解

.NET 7 AOT 使用及 .NET 与 Go 语言互操作详解 目录 .NET 7 AOT 使用及 .NET 与 Go 语言互操作详解 一、背景与技术概述 1.1 AOT 编译技术简介 1.2 Go 语言与 .NET 的互补性 二、.NET 7 AOT 编译实践 2.1 环境准备 2.2 创建 AOT 项目 2.3 AOT 编译流程 2.4 调试信息处…

Shortest path 代码

Project https://graphics.cs.utah.edu/research/projects/shortest-path-to-boundary/ Build and Debug Fork:(在Win10上&#xff09; https://github.com/chunleili/Shortest-Path-to-Boundary-for-Self-Intersecting-Meshes commit hash d3160168d2b6a58188d12e6cd959da…

Spring框架学习day1--基础概念

Spring基础部分**轻量级的**IOC&#xff1a;控制反转&#xff08;对象由自己管理变成交给框架管理&#xff09;AOP&#xff1a;面向切面编程一站式BaenSpring体系结构 Spring Hello World 搭建 Spring基础部分 Spring是一个轻量级的IOC、AOP的一站式java开发框架&#xff0c;为…

立志成为一名优秀测试开发工程师(第九天)——使用fiddler工具、request库进行接口测试

接口测试学习 目录 一、接口测试的介绍 二、抓包软件Fiddler的使用 三、使用Python的Request库发送get、post请求&#xff1a; 1.get请求 2.post请求 四、总结 登录接口实现 认证请求处理 异常处理 高级配置 接口测试工具类封装 测试用例设计规范 Cookie处理方案 …

【面板数据】各地区新型数字基础设施数据集(2002-2025年)

新型数字基础设施是利用新一代信息技术&#xff08;如5G、人工智能、物联网、大数据、区块链等&#xff09;构建的基础设施体系&#xff0c;主要服务于信息传输、计算存储、智能分析和融合应用等环节。新型数字基础设施作为引领经济社会数字化转型的重要支撑&#xff0c;在各地…

小程序 - 视图与逻辑

个人简介 👨‍💻‍个人主页: 魔术师 📖学习方向: 主攻前端方向,正逐渐往全栈发展 🚴个人状态: 研发工程师,现效力于政务服务网事业 🇨🇳人生格言: “心有多大,舞台就有多大。” 📚推荐学习: 🍉Vue2 🍋Vue3 🍓Vue2/3项目实战 🥝Node.js实战 🍒T…

zynq ad7616 调试笔记

环境 zynq7020 ps端的spi外接硬件的ad7616模块&#xff08;非ip核&#xff09; 1 下载 https://github.com/analogdevicesinc/no-OS/blob/2019_R1/ad7616-sdz/ad7616_sdz.c 2 在zynq的sdk中新建ad7616的app程序&#xff0c;将上图方框中的文件拷贝过来并编译 3 移植 ad7616…

如何轻松将 iPhone 备份到外部硬盘

当您的iPhone和电脑上的存储空间有限时&#xff0c;您可能希望将iPhone备份到外部硬盘上&#xff0c;这样可以快速释放iPhone上的存储空间&#xff0c;而不占用电脑上的空间&#xff0c;并为您的数据提供额外的安全性。此外&#xff0c;我们还提供 4 种有效的解决方案&#xff…

从Homebrew找到openssl.cnf文件并拷贝到Go项目下使用

安装OpenSSL 在 macOS 上下载和安装 OpenSSL 最常见和推荐的方式是使用 Homebrew&#xff0c;这是一个 macOS 缺失的包管理器。 如果您还没有安装 Homebrew&#xff0c;请先安装它。安装 Homebrew 后&#xff0c;安装 OpenSSL 只需要一条命令。 步骤 1&#xff1a;安装 Home…

Socket编程基础

这篇博客我们主要用来讲解TCP和UDP的socket编程。 概念 大多数网络协议都是由软件实现的&#xff08;特别是协议栈中的高层协议&#xff09;&#xff0c;而且绝大多数计算机系统都将运输层以下的网络协议在操作系统的内核中进行实现。应用程序要想执行网络操作&#xff0c;必…

什么是物化视图(Materialized View)?

分析师和工程师经常面临一个共同的困境&#xff1a;随着数据量激增&#xff0c;查询性能急剧下降。一个看似简单的多表联合查询可能需要耗时数分钟甚至数小时&#xff0c;一个常规的实时大屏可能因数据处理缓慢而失去时效价值。当数据规模从 GB 级增长到 TB 甚至 PB 级时&#…

USB Redirector对比国产USB Server方案:软硬之争与技术突围

在远程设备共享领域&#xff0c;‌USB Redirector‌以其轻量级跨平台特性长期占据国际主流市场&#xff0c;支持Windows/Linux系统下的无缝USB设备网络共享。然而&#xff0c;随着国产化替代需求的爆发&#xff0c;以‌朝天椒USB Server‌为代表的软硬一体化方案&#xff0c;凭…

PP-OCRv5 C++封装DLL C#调用源码分享

目录 说明 效果 C#调用效果 项目 C# C 头文件 源文件 C#调用 下载 说明 C封装DLL&#xff0c;C#调用源码分享 效果 C#调用效果 项目 C# C 头文件 #include <windows.h> #include <iostream> #include <opencv2/opencv.hpp> #include <stri…

RISC-V PMA、PMP机制深入分析

1 PMA PMA&#xff08;Physical Memory Attributes&#xff09;&#xff0c;物理内存属性&#xff0c;顾名思义就是用来设置物理内存属性的&#xff0c;但这里说“设置”&#xff0c;并不合理&#xff0c;因为一般情况下各存储的属性&#xff0c;在芯片设计时就固定了&#xf…

桂花网体育运动监测方案:开启幼儿园运动健康管理新篇章

在幼儿教育领域&#xff0c;运动能力的培养与健康监测始终是备受关注的核心环节。随着科技的飞速发展&#xff0c;如何科学、有效地监测幼儿的运动状态&#xff0c;成为了幼儿园教育者面临的一大挑战。桂花网体育运动监测方案凭借其高效、精准、智能化的特性&#xff0c;为幼儿…

第6讲、 Odoo 18 `tools` 模块深度分析

Odoo 18 中的 odoo/tools 目录是核心工具模块的集合&#xff0c;封装了大量通用功能&#xff0c;包括数据处理、安全校验、缓存优化、文件处理、时间转换、国际化、多线程处理等。这些工具模块在整个 Odoo 框架中被频繁引用&#xff0c;是系统高效运行和代码解耦的重要基础。 &…