C#异步编程案例学习——异步加载大资源文件1 之 使用 BeginInvoke 与 EndInvoke

2023-10-27

本文主要是根据一个项目中遇到的异步的小例子,研究学习异步编程模型(APM)模式如何使用 BeginInvoke 与 EndInvoke。

C# 中 BeginInvoke 与 EndInvoke 的一个简单的使用案例

在使用 WPF 开发桌面软件过程中,遇到一个需要预加载大量文件的需求,具体需求如下:

  • 在软件启动后,需要加载大量的 CAD 资源文件,文件供软件中后续使用。
  • 文件加载过程中非常耗时,若同步加载则界面会出现假死的状态。
  • 在文件加载过程中,需要保证界面仍然可以进行其他操作,屏蔽跟资源文件有关的操作。
  • 文件加载完成后,开放跟资源文件有关的操作。

解决方案:
在软件启动后,软件页面正常完成加载后,后台继续进行异步加载资源文件,此时可操作正在加载的资源文件的按钮设置为不可操作。文件加载完成之后开发操作资源文件的按钮可操作。

具体实现如下:

1、WPF 页面 Window 中存在一个事件:ContentRendered。该事件在窗口的内容呈现完毕之后发生。定义一个方法,方法中进行文件加载的操作,并将该方法绑定到 ContentRendered 事件。
代码如下:
其中,LoadingResources() 方法为实际加载文件的方法;
ShowProgressBarFile 方法中具体实现异步,并更改页面按钮状态

private void Window_ContentRendered(object sender, EventArgs e)
{
    //禁用对应的操作按钮
    this.btnThreeView.IsEnabled = false;

    Func<bool> action = (() => LoadingResources());
    ShowProgressBarFile(action, "文件加载中...");
}

2、方法 LoadingResources(),实现文件加载的功能。
文件加载使用多线程编程。一个线程为真加载线程,加载对应的资源文件,文件完成加载后,对应页面的操作按钮放开显示;
一个线程为假加载线程,可以用于加载进度条显示在页面。
具体详细的学习和分析见文章:
C#异步编程案例学习——异步加载大资源文件 2 之 多线程加载

 private bool LoadingResources()
 {
 	//......实现具体文件加载
    Thread t1 = new Thread(new ThreadStart(loadReal));  //loadReal 是真线程加载
    t1.Name = "真加载线程";
    t1.IsBackground = false;
    Thread t2 = new Thread(new ThreadStart(loadNotTrue));  //loadNotTrue 是假线程加载
    t2.Name = "假加载线程";
    t2.IsBackground = true;
    t1.Start();
    t2.Start();

    while (trueLoad == 0 && falseLoad == 0)
    {
        //阻塞
    }
    //下面的代码是在完成加载后,屏蔽页面假线程加载显示的进度条信息
    this.pb_info.Dispatcher.Invoke(DispatcherPriority.Normal, (ThreadStart)delegate ()
    {
        this.pb_info.Visibility = Visibility.Collapsed;
    });
    this.pb_test.Dispatcher.Invoke(DispatcherPriority.Normal, (ThreadStart)delegate ()
    {
        this.pb_test.Visibility = Visibility.Collapsed;
    });
    return true;
 }

3、方法ShowProgressBarFile 实现异步:
其中,func.BeginInvoke 开启异步;
asyncResult.IsCompleted 用于检查异步方法是否执行完成;
func.EndInvoke 结束异步。
这种异步编程的方法叫做“异步编程模型(APM)模式”

private bool ShowProgressBarFile(Func<bool> func, string text)
{
    var asyncResult = func.BeginInvoke(null, null);
    while (!asyncResult.IsCompleted)
    {
        System.Windows.Forms.Application.DoEvents();
    }
    
    return func.EndInvoke(asyncResult);
}

异步编程模型模式 APM

.NET Framework 中提供了执行异步操作的 3 种模式:

  • 基于任务的异步模式(Task-based Asynchronous Pattern, TAP)使用一种方法来表示异步操作的启动和完成。TAP 是在 .NET Framework 4 中引入的。C# 中的 async 和 await 关键词为 TAP 添加了语言支持。
  • 异步编程模型(Asynchronous Programming Model)模式(也称 IAsyncResult模式),在此模式中异步操作需要 Beginxxx 和 Endxxx 方法。
  • 基于事件的异步模式(Event-based Asynchronous Pattern,EAP),这种模式需要 Async 后缀,也需要一个或多个事件、事件处理程序委托类型和 EventArg 派生类型。

APM 和 EAP 两种模式是早期版本引入的,.Net 4 版本之后,Visual Studio 建议对于新的开发工作不再使用它们。对于 TAP 在《C#异步编程学习笔记》的文章中有相关学习笔记,后续详细学习使用后补充学习博客。
本文主要介绍的 BeginInvoke 与 EndInvoke 方式是基于 APM 的。.net中有很多类实现了该模式(比如HttpWebRequest),同时我们也可以自定义类来实现APM模式(继承IAsyncResult接口并且实现BeginXXX和EndXXX方法)。

异步编程中的 BeginInvoke 和 EndInvoke

运行原理

如果委托对象的调用列表中只有一个方法(引用方法),就可以异步执行这个方法,通过调用委托类特有的两个方法 BeginInvoke 和 EndInvoke 去执行。
当我们调用委托的 BeginInvoke 方法时,它开始在一个独立线程上执行引用方法,并且立即返回到原始线程。原始线程可以继续,而引用方法会在线程此的线程中并行执行。
当程序希望获取已完成的异步方法的结果时,可以检测 BeginInvoke 返回的 IAsyncResult 的 IsCompleted 属性,或调用委托的 EndInvoke 方法来等待委托完成。

BeginInvoke方法

 IAsyncResult   BeginInvoke(-------,AsyncCallback callback,object state) 
  1. 在调用BeginInvoke时,参数列表中的实参组成如下:
    1. 引用方法需要的参数。
    2. 两个额外的参数——callback参数和state参数。
      1. callback,回调方法的名字;
      2. state可以为null,或发送给回调函数的参数的对象,参数类型是object,在回调函数中必须转换成正确的类型。
  2. BeginInvoke从线程池中获取一个线程并且在新的线程开始时运行引用方法。
  3. BeginInvoke返回给调用线程一个实现IAsyncResult接口的对象。这个接口引用包含了异步方法的当前状态,原始线程然后可以继续执行。

EndInvoke方法

  1. 它接受一个由BeginInvoke方法返回的IAsyncResult对象的引用,并找到它关联的线程。
  2. 如果线程池的线程已经退出,EndInvoke做如下的事情。
    1. 它清理退出线程的状态并且释放它的资源。
    2. 它找到引用方法返回的值并且把它的值作为返回值。
  3. 如果当EndInvoke被调用时线程池的线程仍然在运行,调用线程就会停止并等待,直到清理完毕并返回值。因为EndInvoke是为开启的线程进行清理,所以必须确保对每一个BeginInvoke都调用EndInvoke。
  4. 如果异步方法触发了异常,在调用EndInvoke时会抛出异常。

AsyncResult类

当调用委托的BeginInvoke方法时,系统会创建一个AsyncResult类对象, BeginInvoke方法执行完后,方法返回属于AsyncResult类对象中的一个IAsyncResult接口类型的引用。IAsyncResult 是使用 BeginInvoke 和 EndInvoke 方法的必要部分。
在这里插入图片描述

  1. 当我们调用委托对象的BeginInvoke方法时,系统创建了一个AsyncResult类的对象。然而,它不返回类的对象的引用,而是返回对象中包含的IAsyncResult接口的引用。
  2. AsyncResult对象包含一个叫做AsyncDelegate的属性,它返回一个指向被调用来开启异步方法的委托的引用。但是这个属性是类对象的一部分而不是接口的一部分。
  3. IsCompleted属性返回一个布尔值,表示异步方法是否完成。
  4. AsyncState属性返回一个对象的引用,它被作为 BeginInvoke 方法调用时的state参数。它返回object类型的引用。

BeginInvoke和EndInvoke 的三种模式

  • 等待模式,在发起了异步方法以及做了一些其它处理之后,原始线程就中断,并且等待异步方法完成之后再继续。
  • 轮询模式,原始线程定期检查发起的线程是否完成,如果没有则可以继续做一些其它的事情。
  • 回调模式,原始线程一直在执行,无需等待或检查发起的线程是否完成。在发起的线程中的引用方法完成之后,发起的线程就会调用回调方法,由回调方法在调用EndInvoke之前处理异步方法的结构。

等待模式

在这种模式里,原始线程发起一个异步方法的调用,做一些其它处理,然后停止并等待,直到开启的线程结束。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
 
namespace waitModel
{
	// 1、声明一个委托
    delegate int MyDel(int x, int y);
    class Program
    {
        static int sum(int x,int y)              //异步方法
        {
            Console.WriteLine("inside sum");
            Thread.Sleep(8000);
            return x+y;
        }
        static void Main(string[] args)
        {
			// 2、创建委托变量
			// 3、为委托绑定一个方法
            MyDel Del = new MyDel(sum);
            Console.WriteLine("Before BeginInvoke"); 
            // 4、异步调用委托。
            //	开始异步执行,封装回调函数:BeginInvoke(参数,回调函数,回调函数多需的对象/参数)
            IAsyncResult iresult = Del.BeginInvoke(4,6,null,null);      //开始异步执行 
            Console.WriteLine("After BeginInvoke");                //程序在执行异步方法的同时,执行该语句                
            Console.WriteLine("Doing something");
            int result = Del.EndInvoke(iresult);                        //等待,直到异步方法执行完毕,将结果
            Console.WriteLine("After EndInvoke, result = {0}",result);
            Console.ReadKey();
        }
    }
}
异步操作步骤(等待模式/轮询模式)

异步操作代码实现一般需要经过以下四个步骤,这四个步骤适用于等待模式和轮询模式。

  1. 声明一个委托类型
  2. 创建委托类型的变量
  3. 为委托绑定一个方法(返回值和参数列表与委托相同)
  4. 异步调用委托

如上四个步骤均在等待模式和轮询模式的代码中标注

轮询模式

在轮询模式中,原始线程发起了异步方法的调用,做一些其它处理,然后使用 IAsyncResult 对象的 IsCompleted 属性来定期检查开启的线程是否完成。如果异步方法已经完成,原始线程就调用 EndInvoke 并继续。否则,它做一些其它处理,然后过一会儿再检查。

轮询模式与等待模式相比,只增加了一步:通过检查 IAsnyResult 接口对象的 IsComplete 属性的判断异步方法是否完成,如没完成,在检查过程中可以做其它事情。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
//轮询模式与等待一直完成模式相比只增加了iresult.IsCompleted判断是否完成的步骤
namespace polling
{
    class Program
    {
    	// 1、声明一个委托
        delegate int MyDel(int x,int y);
        static int sum(int x, int y)
        {
            Console.WriteLine("insid sum");
            Thread.Sleep(1000);
            return x + y;
        }
        static void Main(string[] args)
        {
			// 2、创建委托变量
			// 3、为委托绑定一个方法
            MyDel Del = new MyDel(sum);
            Console.WriteLine("Before BeginInvoke");
            // 4、异步调用委托
            IAsyncResult iresult =  Del.BeginInvoke(2,3,null,null); //发起异步调用
            Console.WriteLine("After BeginInvoke");
            while (!iresult.IsCompleted)                           //检测IAsyncResult接口的IsCompleted属性判断异步方法是否完成   
            {
                Console.WriteLine("Sum Not Complete");
            	Thread.Sleep(1000);
            }
            
            Console.WriteLine("Sum Completed."); 
            int result = Del.EndInvoke(iresult);                  //异步方法执行完后才会执行这一句  
            Console.WriteLine("Result: {0}",result);
            Console.ReadKey();
        }
    }
}

回调模式

上面使用等待和轮询模式的方式需要等待。等待是个让人很恼火的事情。.Net考虑了这一点,为我们准备了回调的方式:你异步调用后继续干你的事儿,等你执行完后,你告我一声就ok了。

回调模式的不同之处在于,一旦主线程线程发起了异步方法,它就自己管自己了,不再考虑异步方法是否执行完成,当异步方法执行完后,系统会通过调用一个回调方法处理异步方法的结果,和调用委托的 EndInvoke 方法。

回调方法:回调方法的返回类型为void,以一个IAsyncResult接口类型的参数做为形参:

void AsyncCallback(IAsyncResult iresult)

上文提到 BeginInvoke方法的参数意义

 IAsyncResult BeginInvoke(-------,AsyncCallback callback,object state) 
  • 第一个参数callback,回调方法的名字
  • 第二个参数state可以为null,或发送给回调函数的参数的对象,参数类型是object,在回调函数中必须转换成正确的类型。

回调方法和BeginInvoke方法是通过BeginInvoke方法内部的最后两个参数关联的

两种方法为BeginInvoke方法第一个参数赋值:

  1. 使用回调方法创建 AsyncCallback 类型的委托
  2. 只提供回调方法的名称,编译器会自动创建委托

回调模式是在回调方法内部,通过调用委托的 EndInvoke 方法来处理异步方法返回的结果,释放线程所占用的资源,而等待模式和轮询模式是在主线程中调用的;

要调用委托的EndInvoke方法,必须要有委托的引用,在回调方法内部,先是将回调方法的 IAsyncResult 接口类型通过里氏转换转换为 AsyncResult 类类型,然后再调用 AsyncResult 类的 AsyncDelegate 属性转化为合适的委托类型。

  • 给回调方法的参数只有一个,就是刚结束的异步方法的IAsyncResult接口的引用,要记住,IAsyncResult接口对象在AsyncResult类对象的内部。
  • 尽管IAsyncResult接口没有委托对象的引用,而包含它的AsyncResult类对象却有委托对象的引用。
  • 有了类对象的引用,我们现在就可以调用类对象的AsyncDelegate属性并且把它转化为合适的委托类型。这样就得到了委托引用,我们可以用它来调用EndInvoke。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Remoting.Messaging;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
 
namespace callback
{
 
    delegate int Mydel(int x,int y);
    class Program
    {
        static int sum(int x,int y)                 //异步方法
        {
            Console.WriteLine("                   InSide Sum1");
            Thread.Sleep(1000);
            Console.WriteLine("                   InSide Sum2");
            return x + y;
        }
        //回调模式是在回调方法内部,通过调用委托的EndInvoke方法来处理异步方法返回的结果,释放线程所占用的资源而等待直到完成模式和轮询模式是在主线程中调用;
        //要调用委托的EndInvoke方法,必须要有委托的引用,在回调方法内部,先是将回调方法的IAsyncResult接口类型通过里氏转换转换为AsyncResult类类型,
        //然后再调用AsyncResult类的AsyncDelegate属性转化为合适的委托类型
 
 
        static  void callback_(IAsyncResult iar)    //回调方法        无论如何回调方法中的内容必须在异步方法执行完后才执行    
        {
            Console.WriteLine("                     Inside CallWhenDone");
            AsyncResult ar = (AsyncResult)iar;      //里氏转换,如果父类中装的是子类的对象,那么可以将这个父类转换为子类对象   
            Mydel del = (Mydel)ar.AsyncDelegate;    //通过AsyncResult类对象的AsyncDelegate属性转化为合适的Mydel委托类型
            long result = del.EndInvoke(iar);
            Console.WriteLine("                     The result is: {0}",result);
 
        }
        static void Main(string[] args)
        {
            Mydel Del = new Mydel(sum);
            Console.WriteLine("Before BeginInvoke");
            IAsyncResult iar = Del.BeginInvoke(7,8,new AsyncCallback(callback_),Del);                   //Del焕成null结果也一样
            Console.WriteLine("Doing more work in main");
            Thread.Sleep(500);
            Console.WriteLine("Done with Main.Exiting");
            Console.ReadKey();
        }
    }
}
异步操作步骤(回调模式)

异步操作代码(回调模式)相较于前面的增加了一个回调,这三个步骤适用于等待模式和轮询模式。

  1. 声明一个委托类型
  2. 创建委托类型的变量
  3. 为委托绑定一个方法(返回值和参数列表与委托相同)
  4. 异步调用委托
  5. 回调函数
	 //【1】声明一个名为Operation的委托,并交代执行方法
	 // 这里完成了1. 声明一个委托类型;2. 创建委托类型的变量;3. 为委托绑定一个方法
 	 //int:传入参数  string:返回值
        public Func<int, string> Operation = (num) => 
        {
            Thread.Sleep(num * 1000);
            return num.ToString();
        };
        private void button1_Click(object sender, EventArgs e)
        {
            //【2】异步调用
            IAsyncResult str = Operation.BeginInvoke(2, CallBack, "发送给回调函数的参数");
        }
        //【3】回调函数
        public void CallBack(IAsyncResult result)
        {
            string res = Operation.EndInvoke(result); //返回结果(结果类型,与委托返回值类型一致)
            var v = result.AsyncState;	//获取传递给回调函数的参数
        }

笔记参考
http://www.cnblogs.com/zwt-blog/p/4812530.html#3611906
https://blog.csdn.net/ABC13222880223/article/details/85317621

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C#异步编程案例学习——异步加载大资源文件1 之 使用 BeginInvoke 与 EndInvoke 的相关文章

随机推荐

  • VNC源码研究(十)XP、Win7实现模拟发送ATL+CTRL+DEL

    转载请标明是引用于 http blog csdn net chenyujing1234 欢迎大家拍砖 1 vnc 4 0 winsrc 版本中实现模拟发送ATL CTRL DEL 在工程wrfb win32m中找到模拟发送ATL CTR D
  • Doris学习笔记之查询

    文章目录 查询设置 增大内存 修改超时时间 查询重试和高可用 代码方式 JDBC连接器 ProxySQL方式 简单查询 基本查询 联合 join 查询 子查询 Join查询 广播Join 默认使用 Broadcast Join 显式使用 B
  • Android程序如何全屏显示

    在一个8寸屏的Android平板电脑上开发应用程序 游戏程序 开始的时候 总是不能全屏显示 也不知道怎么设置才可以 本来LCD应该是800 600的 但总是得到600 600的结果 修改代码如下 布局文件 02
  • 005--Keil使用--出现integer conversion resulted in truncation

    warning 69 D integer conversion resulted in truncation 问题所在 变量赋值超出了变量类型的最大值 解决方法 找到该变量 适当修改变量的类型
  • android u3d验证,几种方法验证unity是否为development build

    我在月初接入了uwa的性能测试SDK 需要提交一个development build的游戏安装包给uwa进行真人真机测试 本文说下如何判断安装包是否为development build 直观上判断 如果是development build模
  • cobra的使用

    一 安装cobra go get g github com spf13 cobra cobra 这里会报错 如下 package golang org x sys unix unrecognized import path golang o
  • Qtcreator远程调试出现“The selected build of GDB does not support Python scripting.It cannot be used .."...

    版权声明 本文为博主原创文章 未经博主允许不得转载 https blog csdn net aristolto article details 77370853 之前使用的是Qt4 7后来换成了Qt5 x Qtcreator的版本使用4 0
  • 基数统计—— HyperLogLog 算法

    目录 基数计数基本概念 基数计数方法 B树 bitmap 概率算法 HLL 直观演示 HLL 的实际步骤 算法来源 N次伯努利过程 解释 LogLogCounting 均匀随机化 分桶平均 偏差修正 误差分析 算法应用 误差控制 内存使用分
  • 无线传感网WSN

    第一章 绪论 WSN定义 无线传感网络是由大量的静止或移动的传感器以自组织和多跳的方式构成的无线网络 WSN系统组成 传感器节点 汇聚节点和管理节点 WSN的三个基本要素 传感器 感知对象 用户 观测者 WSN特点 1 自组织性 2 以数据
  • docker添加新的环境变量_在Dockerfile中,如何更新PATH环境变量?

    I have a dockerfile that download and builds GTK from source but the following line is not updating my image s environme
  • linux下添加简单的开机自启动脚本

    在rc local脚本中添加开机自启动程序 1 编辑文件 etc rc local vim ect rc local 2 编辑完 etc rc local需要添加执行权限 否则调用失败 chmod x ect rc d rc local 3
  • 信息学奥赛C++语言: 验证子串

    题目描述 输入两个字符串 验证其中一个串是否为另一个串的子串 输入 输入两个字符串 每个字符串占一行 长度不超过200且不含空格 输出 若第一个串s1是第二个串s2的子串 则输出 s1 is substring of s2 否则 若第二个串
  • Mac开发环境搭建

    一 jdk安装 1 1 jdk下载 首先查看你的Mac 系统是否已经安装了JDK 打开终端窗口并输入java version 如果没有安装过jdk会提示需要安装 Java Downloads Oracle 小技巧 mac如何打开终端 Com
  • CTFshow刷题记录

    https shimo im docs hJvkwcW8XvPxqyQK ctfshow misc
  • open【部署、使用教程】

    目录 1 创建证书 2 安装openVPN Server端并配置 3 将证书移动到相对路径 4 开启内核转发功能 否则会无法启动openVPN 5 启动服务 加入开机自启 6 启动后服务端会生成一个tun0的虚拟网卡 用于不同网段之间相互通
  • 让CSS flex布局最后一行列表左对齐的N种方法

    写在前面 精简版 可以直接移步我的另一篇博客 适用于行列数都不确定的情况 https blog csdn net HD243608836 article details 129854063 一 justify content对齐问题描述 在
  • 通信总线协议一 :UART

    文章目录 通信总线协议一 UART 1 通信基础 1 1 并行 串行 1 2 单工 双工 1 3 同步 异步 1 4 波特率 2 Uart通信协议 3 硬件连接 4 uart存在的问题 通信总线协议一 UART 1 通信基础 1 1 并行
  • 如何在线将Ubuntu 18.04升级到Ubuntu 20.04

    将Ubuntu 18 04升级到Ubuntu 20 04 在将系统升级运行到下一个主要发行版本之前 请确保已备份当前系统 以使您可以恢复原状 以防万一 备份过程不在本指南的范围内 运行系统更新 首先将Ubuntu 18 04系统软件包更新和
  • 深入学习jquery源码之trigger()与triggerHandler()

    深入学习jquery源码之trigger 与triggerHandler trigger type data 概述 在每一个匹配的元素上触发某类事件 这个函数也会导致浏览器同名的默认行为的执行 比如 如果用trigger 触发一个 subm
  • C#异步编程案例学习——异步加载大资源文件1 之 使用 BeginInvoke 与 EndInvoke

    C 异步编程案例学习 异步加载大资源文件1 之 使用 BeginInvoke 与 EndInvoke C 中 BeginInvoke 与 EndInvoke 的一个简单的使用案例 异步编程模型模式 APM 异步编程中的 BeginInvok