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

news2025/6/1 1:51:47

文章目录

    • 引言
    • 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);
        
        // 安全地更新UI
        this.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.coloradmin.cn/o/2392164.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

告别延迟!modbus tcp转profine网关助力改造电厂改造升级

发电需求从未如此旺盛。无论您是为客户发电还是为自身运营发电&#xff0c;您都需要提高运营效率&#xff0c;并在资产老化、资源萎缩的情况下&#xff0c;紧跟不断变化的法规。如今&#xff0c;智能系统和技术能够帮助您实现运营转型&#xff0c;提高可视性并实现关键流程自动…

《软件工程》第 5 章 - 需求分析模型的表示

目录 5.1需求分析与验证 5.1.1 顺序图 5.1.2 通信图 5.1.3 状态图 5.1.4 扩充机制 5.2 需求分析的过程模型 5.3 需求优先级分析 5.3.1 确定需求项优先级 5.3.2 排定用例分析的优先顺序 5.4 用例分析 5.4.1 精化领域概念模型 5.4.2 设置分析类 5.4.3 构思分析类之间…

阿里云国际版香港轻量云服务器:CN2 GIA加持,征服海外网络的“速度与激情”!

阿里云国际版香港轻量云服务器&#xff1a;CN2 GIA加持&#xff0c;征服海外网络的“速度与激情”&#xff01; 面对全球化业务拓展对网络连接的严苛要求&#xff0c;阿里云国际版香港轻量云服务器正成为出海企业和开发者的新宠。其核心优势在于搭载了CN2 GIA&#xff08;Glob…

Qt6无法识别OpenCV(Windows端开发)

这段时间在Windows 10上进行Qt6的开发。结果在build过程中&#xff0c;出现了如下错误: 但实际上&#xff0c;我明明安装了OpenCV4.10.0, 并且也在CMakeLists.txt中加入了相关内容。 但是&#xff0c;注意自己的编译输出: [1/5 1.4/sec] Automatic MOC and UIC for target R…

二、网络安全常见编码及算法-(2)

该文章主要介绍古典密码和隐写常用的密码和编码&#xff0c;日常中很少见&#xff0c;主要用于ctf比赛和考试学习一、古典密码 1、古典密码概念概述 古典密码是密码学发展早期所使用的一系列加密技术&#xff0c;这些密码主要依靠手工操作或简单的机械装置来实现信息的加密和…

Windows系统安装MySQL Connector 使用C++ VS2022连接MySQL

1. 官网及版本 1.1. 网址 官方文档 - 安装编译构建&#xff1a; https://dev.mysql.com/doc/connector-cpp/9.3/en/ 官方文档 - 使用案例&#xff1a; https://dev.mysql.com/doc/dev/connector-cpp/latest/ 下载地址&#xff1a; https://dev.mysql.com/downloads/connector/…

D2000平台上Centos使用mmap函数遇到的陷阱

----------原创不易&#xff0c;欢迎点赞收藏。广交嵌入式开发的朋友&#xff0c;讨论技术和产品------------- 在飞腾D2000平台上&#xff0c;安装了麒麟linux系统&#xff0c;我写了个GPIO点灯的程序&#xff0c;在应用层利用mmap函数将内核空间映射到用户态&#xff0c;然后…

Elasticsearch索引机制与Lucene段合并策略深度解析

引言 在现代分布式搜索引擎Elasticsearch中&#xff0c;文档的索引、更新和删除操作不仅是用户交互的核心入口&#xff0c;更是底层存储架构设计的关键挑战。本文围绕以下核心链路展开&#xff1a; 文档生命周期管理&#xff1a;从客户端请求路由到分片定位&#xff0c;从内存…

整合Jdk17+Spring Boot3.2+Elasticsearch9.0+mybatis3.5.12的简单用法

Elasticsearch是一个基于Lucene的分布式搜索和分析引擎&#xff0c;广泛应用于全文搜索、日志分析等场景。结合Spring Boot可以快速构建强大的搜索应用。本文将介绍如何在Spring Boot项目中集成和使用Elasticsearch。 ES9.0.1目前支持的包只有 elasticsearch-rest-client/ …

Ubuntu从0到1搭建监控平台:本地部署到公网访问实战教程Cpolar穿透与Docker部署全过程

文章目录 前言1.关于Ward2.Docker部署3.简单使用ward4.安装cpolar内网穿透5. 配置ward公网地址6. 配置固定公网地址总结 前言 IT运维人员是否常为服务器管理系统的复杂操作所困扰&#xff1f;当海量性能指标图表与密集预警信号同时涌现时&#xff0c;这种信息过载往往让专业团…

vscode java debug terminal 中文乱码

现象 解决 快捷键 ctrl , 进入setting 配文件添加 "terminal.integrated.automationProfile.windows": {"path": "cmd","args": ["/k","chcp","65001"]}terminal 启动时&#xff0c;活动也改为 utf-…

3D PDF如何制作?SOLIDWORKS MBD模板定制技巧

SOLIDWORKS制作3D PDF模版 SOLIDWORKS MBD能够帮助工程师以清晰直观的方式描述产品尺寸信息。在3D PDF文件中&#xff0c;用户可以自由旋转和移动视图&#xff0c;方便查看模型的各个尺寸细节。 本文将带您一步步学习如何使用SOLIDWORKS MBD制作专业的3D PDF模板&#xff0c;…

Qt DateTimeEdit(时间⽇期的微调框)

使⽤ QDateEdit 作为⽇期的微调框. 使⽤ QTimeEdit 作为时间的微调框 使⽤ QDateTimeEdit 作为时间⽇期的微调框. 这⼏个控件⽤法⾮常相似, 我们以 QDateTimeEdit 为例进⾏介绍. QDateTimeEdit 核⼼属性 属性说明dateTime时间⽇期的值. 形如 2000/1/1 0:00:00date单纯⽇期…

C# 类和继承(屏蔽基类的成员)

屏蔽基类的成员 虽然派生类不能删除它继承的任何成员&#xff0c;但可以用与基类成员名称相同的成员来屏蔽&#xff08;mask&#xff09; 基类成员。这是继承的主要功能之一&#xff0c;非常实用。 例如&#xff0c;我们要继承包含某个特殊方法的基类。该方法虽然适合声明它的…

基于vue框架的动物园饲养管理系统a7s60(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;饲养员,健康登记,工作进度,动物信息,进食信息,动物健康,动物医治,饲料信息,工作留言 开题报告内容 基于Vue框架的动物园饲养管理系统开题报告 一、研究背景与意义 &#xff08;一&#xff09;研究背景 随着城市化进程加快和公众对生…

WPS自动换行

换行前 换行后 快捷键 第一步&#xff1a;启用「自动换行」功能 选中目标单元格/区域&#xff1a;点击需要设置的单元格&#xff08;或拖动选中多个单元格&#xff09;。开启自动换行&#xff08;3种方式任选&#xff09;&#xff1a; 快捷按钮&#xff1a;在顶部菜单栏点击「…

maven中的grpc编译插件protobuf-maven-plugin详解

protobuf-maven-plugin 是 Maven 中用于编译 Protocol Buffers&#xff08;protobuf&#xff09;文件并生成对应语言代码&#xff08;如 Java、C、Python 等&#xff09;的插件。在 gRPC 项目中&#xff0c;它常被用来生成服务端和客户端所需的代码。以下是该插件的详细解析&am…

服务发现Nacos

目录 Nacos server 安装 注册服务到Nacos server 接口访问Nacos server中的已注册服务 Nacos控制台介绍 Nacos:一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。 在分布式服务应用中&#xff0c;各类服务需要统一的注册、统一的管理&#xff0c;这个组件工具…

社群分享:义乌|杭州电商|店群卖家,私域鱼塘运营的排单系统开源|私域鱼塘运营|返款软件开源

熟悉东哥的朋友都知道&#xff0c;我自己也运营一个电商社群&#xff0c;主要是针对玩私域|鱼塘的电商玩家。 在当前电商环境下&#xff0c;社群分享型电商、店群卖家及私域鱼塘运营者&#xff0c;面临着日益复杂的订单管理和客服调度问题。传统的人工处理不仅效率低…

C#回调函数深度解析

文章目录 前言什么是回调函数C#中实现回调的方式委托(Delegate)事件(Event)Action和FuncPredicateAsyncCallback匿名方法和Lambda表达式 回调函数实际应用场景异步编程事件处理策略模式LINQ查询 回调函数的优缺点优点缺点 最佳实践与注意事项总结相关资源 前言 在现代软件开发…