前些日子,看到园子里面有人用老王喝茶的例子讲解了一下同步和异步,虽然没有代码实现,但是能够通俗易懂的讲解了同步、异步、阻塞、非阻塞的关系了,今天借题发挥,用一个热水器加热洗澡的例子来具体演示一下C#使用委托进行异步编程。

首先引用MSDN中的一段话来描述一下如何使用异步方式
.NET Framework 允许您异步调用任何方法。 为此,应定义与您要调用的方法具有相同签名的委托;公共语言运行时会自动使用适当的签名为该委托定义 BeginInvoke 和 EndInvoke 方法。

BeginInvoke 方法启动异步调用。 该方法与您需要异步执行的方法具有相同的参数,还有另外两个可选参数。 第一个参数是一个 AsyncCallback 委托,该委托引用在异步调用完成时要调用的方法。 第二个参数是一个用户定义的对象,该对象将信息传递到回调方法。 BeginInvoke 立即返回,不等待异步调用完成。 BeginInvoke 返回一个 IAsyncResult,后者可用于监视异步调用的进度。

EndInvoke 方法检索异步调用的结果。 在调用 BeginInvoke 之后随时可以调用该方法。 如果异步调用尚未完成,则 EndInvoke 会一直阻止调用线程,直到异步调用完成。 EndInvoke 的参数包括您需要异步执行的方法的 out 和 ref 参数(在 Visual Basic 中为 <Out> ByRef 和 ByRef)以及由 BeginInvoke 返回的 IAsyncResult。

上文中提到了一个 IAsyncResult 接口,这个就是今天的主角

public interface IAsyncResult
{object AsyncState { get; }WaitHandle AsyncWaitHandle { get; }bool CompletedSynchronously { get; }bool IsCompleted { get; }
}

IAsyncResult 类型公开以下成员:

AsyncState :获取用户定义的对象,它限定或包含关于异步操作的信息
AsyncWaitHandle :获取用于等待异步操作完成的 WaitHandle
CompletedSynchronously :获取一个值,该值指示异步操作是否同步完成
IsCompleted :获取一个值,该值指示异步操作是否已完成

如果上面的介绍看不明白,没有关系,下面来通过一个例子来进行演示,您一定会搞清晰明白的,先看一下程序主界面图,以便后面的代码说明较好理解。

我们建立的是一个winform程序,我们先用同步的方式来演示一下老王想洗澡这件事,洗澡就得用热水器烧水,因此我们先定义一个热水器类 Heater,代码如下:

Heater热水器代码

public class Heater{/// <summary>/// 设定的温度/// </summary>public int SetTemp { get; set; }/// <summary>/// 当前水温/// </summary>private int _currentTemp;public int CurrentTemp{get { return _currentTemp; }}private bool _flag;public bool Flag{get { return _flag; }}public Heater(){this._flag = false;}/// <summary>/// 烧水/// </summary>public int BoilWater(){Thread.Sleep(5000);for (int i = 0; i <= 100; i++){//Thread.Sleep(50);_currentTemp = i;if (_currentTemp >= SetTemp){_flag = true;break;}}return _currentTemp;}}

Heater类中有属性,设定温度,当前温度和一个水是否烧好的状态布尔值,并在烧水方法中让线程休眠5秒钟,其目的是符合实际情况,烧水总要有个时间过程。

下面我们的老王闪亮登场,老王有两个方法 分别是打开热水器和看电视,代码如下:

老王代码

public class LaoWang{public Heater heater { get; set; }public LaoWang(Heater heater){this.heater = heater;}public int OpenHeater(){ return heater.BoilWater();}public string WatchTv(){return "老王去看电视了...\r\n";}}

然后我们在winform程序中编写我们的主代码,我们在同步调用按钮的点击事件中编写如下代码:

private void btnSync_Click(object sender, EventArgs e)
{this.txtSyncResult.AppendText("老王想洗澡了...\r\n");Heater heater = new Heater();heater.SetTemp = 70;LaoWang laowang = new LaoWang(heater);this.txtSyncResult.AppendText("老王打开了热水器...\r\n");int curTemp = laowang.OpenHeater();//这里阻塞了this.txtSyncResult.AppendText(laowang.WatchTv());if (laowang.heater.Flag){this.txtSyncResult.AppendText("水烧好了...");this.txtSyncResult.AppendText("当前水温 " + curTemp.ToString() + " 度");}
}

代码编写完成,我们运行一下,结果如下:

虽然结果是我们预期的,貌似很合理。但是我们会发现,当程序调用了int curTemp = laowang.OpenHeater() 方法的时候,程序就会发生阻塞,一直在等待返回值,并没有立即执行老王看电视的方法,而是烧水方法完成后并返回当前水温数值之后,才会执行后面的代码。哈哈,这不就说明老王很傻,在烧水准备洗澡的时候,一直再傻傻的等待在热水器旁边,等水烧好了,再去看电视,然后再准备洗澡。这种情况就是我们说的同步阻塞。

那么这种情况如何解决呢?下面聪明的老刘登场了,老刘玩的就是异步,烧水的期间去看电视了,不用傻傻的等着了,代码如下:

老刘代码

public class LaoLiu{/// <summary>/// 热水器类/// </summary>public Heater heater {private get; set; }//定义一个烧水的委托和委托变量private delegate int BoilWaterDelegate();private BoilWaterDelegate _dgBoilWater;public LaoLiu(Heater heater){this.heater = heater;_dgBoilWater = new BoilWaterDelegate(heater.BoilWater);}/// <summary>/// 看电视/// </summary>public string WatchTv(){return "老刘去看电视了...\r\n";}/// <summary>/// 边吃饭边看电视/// </summary>/// <returns></returns>public string ListenToSong(){return "老刘去听音乐了...\r\n";}/// <summary>/// 开始烧水/// </summary>/// <param name="callBack"></param>/// <param name="stateObject"></param>/// <returns></returns>public IAsyncResult BeginBoilWater(AsyncCallback callBack, Object stateObject){try{return _dgBoilWater.BeginInvoke(callBack, stateObject);}catch (Exception e){throw e;}}/// <summary>/// 烧水结束/// </summary>/// <param name="ar"></param>/// <returns></returns>public int EndBoilWater(IAsyncResult ar){if (ar == null)throw new NullReferenceException("IAsyncResult 参数不能为空");try{return _dgBoilWater.EndInvoke(ar);}catch (Exception e){   throw e;}}}

我们在老刘类中主要 声明了个 委托 BoilWaterDelegate,并定义委托变量执行热水器中的加热方法,利用BeginBoilWater 和 EndBoilWater 方法来实现异步调用,这两个方法与MSDN中的陈述是一样一样的。

BeginBoilWater 方法有两个参数:
第一个参数是 AsyncCallback callBack,这就是个回调方法,您可以这么理解,就是异步完成后,调用callBack方法来继续执行
第二个参数用户定义的对象,该对象将信息传递到回调方法中。
返回值是返回一个 IAsyncResult,可以用于监视异步是否完成。
由于我们的烧水方法中,没有ref,out 等参数,因此EndBoilWater 目前只有一个参数,就是 BeginBoilWater 方法返回的 IAsyncResult,EndBoilWater 方法的返回值就是我们热水器类烧水方法的返回值当前温度。

MSDN还说:EndInvoke 方法检索异步调用的结果。 在调用 BeginInvoke 之后随时可以调用该方法。 如果异步调用尚未完成,则 EndInvoke 会一直阻止调用线程,直到异步调用完成

我们试验一下是不是这样呢,运行如下代码:

private void btnAsync_Click(object sender, EventArgs e)
{this.txtAsyncResult.AppendText("老刘想洗澡了...\r\n");Heater heater = new Heater();heater.SetTemp = 85;LaoLiu laoliu = new LaoLiu(heater);this.txtAsyncResult.AppendText("老刘开始烧水...\r\n");IAsyncResult ar = laoliu.BeginBoilWater(null, null);this.txtAsyncResult.AppendText(laoliu.WatchTv());this.txtAsyncResult.AppendText(laoliu.ListenToSong());int curTemp = laoliu.EndBoilWater(ar);this.txtAsyncResult.AppendText("水烧好了...");this.txtAsyncResult.AppendText("当前水温 " + curTemp.ToString() + " 度");
}

结果如下:

在运行过程中,我们会发现 调用BeginBoilWater(内部其实是BeginInvoke)之后,程序没有发生阻塞,而是继续执行老王去看电视,老刘去听音乐去两个方法,当执行到EndBoilWater(内部其实是EndInvoke方法),由于异步操作没有完成,程序还是会发生阻塞,直到异步调用完成,返回数据,这和MSDN的陈述也是一样的。

有没有什么办法可以判断异步是否完成了呢?当然了,这就需要用到 IAsyncResult接口中的属性了。

首先我们用IAsyncResult中的IsCompleted 属性进行轮询判断是否完成,为了时间短一些,我把Heater中加热方法设置成 100 毫秒,我们执行如下代码:

private void btnAsync_Click(object sender, EventArgs e)
{this.txtAsyncResult.AppendText("老刘想洗澡了...\r\n");Heater heater = new Heater();heater.SetTemp = 85;LaoLiu laoliu = new LaoLiu(heater);this.txtAsyncResult.AppendText("老刘开始烧水...\r\n");IAsyncResult ar = laoliu.BeginBoilWater(null, null);this.txtAsyncResult.AppendText(laoliu.WatchTv());this.txtAsyncResult.AppendText(laoliu.ListenToSong());int i = 0;//轮询判断异步是否完成while (!ar.IsCompleted){i++;this.txtAsyncResult.AppendText(" " + i.ToString() + " ");if (ar.IsCompleted){this.txtAsyncResult.AppendText("\r\n水烧好了...\r\n");}}int curTemp = laoliu.EndBoilWater(ar);        this.txtAsyncResult.AppendText("当前水温 " + curTemp.ToString() + " 度");
}

结果如下:

运行过程中,程序没有发生阻塞,我们在while循环中一直不停的判断ar.IsCompleted 状态,并打印i的值,当i=83的时候,异步调用完成了,打印出来了最后的结果

第二种方法,使用 WaitHandle 等待异步调用。

MSDN解释,使用 IAsyncResult.AsyncWaitHandle 属性获取 WaitHandle,使用其 WaitOne 方法阻止执行,直至 WaitHandle 收到信号,然后调用 EndInvoke。

很多人不理解,其实它就是个信号量,当使用其Waitone()方法的时候,程序就会发生阻塞,如果异步完成,Waitone就会返回true,否则返回false。当然最方便的就是我们可以在Waitone中设置一个时间来做超时处理,比如我们可以在 IAsyncResult ar = laoliu.BeginBoilWater(null, null); 代码后增加ar.AsyncWaitHandle.WaitOne(2000),由于,异步方法的线程需要5000ms,主线程等待了2000ms后,认为是超时了,便会继续执行后面老王看电视,老王听音乐的代码。

为了好玩一些,我们把热水器烧水的方法修改一下,把Thread.Sleep(5000); 注释掉,在for 循环中增加Thread.Sleep(50);循环环一次,等待50ms,完整代码如下:

/// <summary>
/// 烧水
/// </summary>
public int BoilWater()
{//Thread.Sleep(5000);for (int i = 0; i <= 100; i++){Thread.Sleep(50);_currentTemp = i;if (_currentTemp >= SetTemp){_flag = true;break;}}return _currentTemp;}

用WaitHandle中waitone来等待异步完成,我们来让看电视的的老刘,每隔一段时间去看一下水是否烧好,代码如下:

private void btnAsync_Click(object sender, EventArgs e)
{this.txtAsyncResult.AppendText("老刘想洗澡了...\r\n");Heater heater = new Heater();heater.SetTemp = 85;LaoLiu laoliu = new LaoLiu(heater);this.txtAsyncResult.AppendText("老刘开始烧水...\r\n");IAsyncResult ar = laoliu.BeginBoilWater(null, null);this.txtAsyncResult.AppendText(laoliu.WatchTv());this.txtAsyncResult.AppendText(laoliu.ListenToSong());//WaitOne 作用 等待句柄bool flag = true;while (flag){this.txtAsyncResult.AppendText(string.Format("老刘去看了一眼,水还没烧好,当前水温 {0} 度...\r\n", heater.CurrentTemp));flag = !ar.AsyncWaitHandle.WaitOne(1000);}this.txtAsyncResult.AppendText("水烧好了...\r\n");int curTemp = laoliu.EndBoilWater(ar);this.txtAsyncResult.AppendText("当前水温 " + curTemp.ToString() + " 度");
}

执行结果如下:

最后我们来演示一下如何在异步中使用回调方法和用户定义对象:

我们来这样做,我们在主界面代码中增加一个显示烧水完成后在文本框显示最终状态的方法ActionCallBack(int curTemp),在老刘类中增加BoilWaterCallBack(IAsyncResult ar) 回调方法,获取异步完成后的结果值。将ActionCallBack方法作为用户自定义对象进行传递到回调函数BoilWaterCallBack 中,在BoilWaterCallBack方法中 获取ActionCallBack 方法,再进行回调,让界面输出结果。

在老王类中新增打开热水器方法OpenHeater 和回调方法BoilWaterCallBack,代码如下:

/// <summary>
/// 打开热水器
/// </summary>
/// <param name="callback"></param>
public void OpenHeater(Action<int> callback)
{BeginBoilWater(BoilWaterCallBack, callback);
}/// <summary>
/// 烧水结束后显示当前的水温
/// </summary>
/// <param name="ar"></param>
private void BoilWaterCallBack(IAsyncResult ar)
{Action<int> callback = ar.AsyncState as Action<int>;int curtemp = EndBoilWater(ar);callback(curtemp);
}

在界面代码中增加ActionCallBack方法:

public void ActionCallBack(int curTemp)
{this.txtAsyncResult.Invoke((MethodInvoker)(() =>{this.txtCallBack.AppendText("水烧好了...\r\n");this.txtCallBack.AppendText("当前水温 " + curTemp.ToString() + " 度");}));
}

由于该方法会在异步线程中执行,因此文本框需要利用invoke方式来进行赋值操作。

在主界面中的异步回调按钮的点击事件中调用该代码:

 private void btnCallBack_Click(object sender, EventArgs e)
{this.txtCallBack.AppendText("老刘想洗澡了...\r\n");Heater heater = new Heater();heater.SetTemp = 85;LaoLiu laoliu = new LaoLiu(heater);this.txtCallBack.AppendText("老刘开始烧水...\r\n");//老刘打开热水器,然后去看电视了
    laoliu.OpenHeater(ActionCallBack);this.txtCallBack.AppendText(laoliu.WatchTv());this.txtCallBack.AppendText(laoliu.ListenToSong());
}

代码运行结果如下:

至此,这个例子就演示完了,不足之处望大家多多指教!例子代码在此下载

转载于:https://www.cnblogs.com/amylis_chen/archive/2012/12/12/2814752.html

也来说说C#异步委托相关推荐

  1. 异步委托实现多线程winform控件编程

            private void button1_Click(object sender, EventArgs e)         {             ThreadStart ts  ...

  2. .NET简谈组件程序设计之(异步委托)

    说到委托我想大家基本上都用过的,今天这篇文章就来讲解关于委托的异步奥秘. 在我们正常使用的时候很少会去用异步委托技术来提高代码效率.委托的好处就是能对方法进行面向对象的封装,随意传递.在任何组件客户代 ...

  3. [转载]C#异步委托的用法 .

    每个委托都有三个方法:Invoke.BeginInvoke.EndInvoke.第一个方法是委托指定函数的同步调用,另外两个是异步调用. BeginInvoke方法,调用后立即返回,不等待调用结果.E ...

  4. 多线程,异步委托,同步委托几种方式的区别

    Code         public delegate void DoThingsDelegate();         private void Window_Loaded(object send ...

  5. C# 异步委托 BeginInvoke EndInvoke

    1. 简单主线程中委托: static void Main(string[] args) {//定义一个委托,并初始化Func<int, int, string> delFunc = (a ...

  6. 界面-工作者线程结构之:异步委托调用

    1.流程: 1.1 定义一个委托,形如:public delegate object WorkThread(object controlsAndParameters);由于object可以容纳任何.N ...

  7. C#异步编程的实现方式(1)——异步委托

    异步的实现即多线程,一种简单的方式就是创建一个委托,然后异步调用它. .Net Framework已经为我们提供了委托的异步调用方法.下面介绍三种使用委托实现异步的方法. 1.投票(IsComplet ...

  8. [转]C#异步编程的实现方式(1)——异步委托

    异步的实现即多线程,一种简单的方式就是创建一个委托,然后异步调用它. .Net Framework已经为我们提供了委托的异步调用方法.下面介绍三种使用委托实现异步的方法. 1.投票(IsComplet ...

  9. C#线程通信与异步委托

    线程的通知机制 AutoResetEvent是线程实现通知操作的重要方法.通常,AutoResetEvent用于通知正在等待线程已发生事件,允许线程通过发信号互相通信. AutoResetEvent时 ...

  10. CAD—定义委托异步添加实体

    先简单的介绍下同步和异步委托: (1)同步委托:委托的Invoke方法用来进行同步调用.同步调用也可以叫阻塞调用,它将阻塞当前线程,然后执行调用,调用完毕后再继续向下进行. (2)异步委托:异步调用不 ...

最新文章

  1. Android 进阶 Fragment 介绍和使用 (一)
  2. 安卓9 怎么运行老程序_这些安卓应用程序一直在后台运行,即使您关了它
  3. 【美文欣赏】人人都有难念的经
  4. SSL 多线程通信 linux openSSL C API编程
  5. python 两个队列进行对比
  6. python怎么输入下一行_python在指定行前插入一行
  7. Kafka负载均衡、Kafka自定义Partition、Kafk文件存储机制
  8. java反射 泛型类型_【译】9. Java反射——泛型
  9. ps软件怎么测试性能,怎么用ps测试电脑性能 设计师要知道
  10. 3 前端面试,js(上)
  11. BUUCTF Cipher
  12. python 随手写的堆排序
  13. 【mysql】 Windows下使用DOS命令进入MySQL数据库
  14. iOS 给APP评分无法连接到App Store解决方法
  15. 张勋说:棒磨机钢棒技术标准应怎样确定(图文)
  16. 一大波折叠屏手机就要来了,你会买吗?
  17. 讯闪2007正式版,菜单自身穿透保存的问题!
  18. simulink电力电子仿真(5)三相桥式全控整流电路
  19. C++实现线性方程组运算函数 LDL法
  20. 传统的 8 小时工作制为什么会效率低下?

热门文章

  1. Eclipse 安装SVN的两种方式
  2. 两分钟实现安全完备的登录模块
  3. sql 在排序后limit 查询疑问
  4. 数据冲突Statspack ORA-00001 unique constraint violated错误的解决
  5. ITerm2的安装和配置
  6. FileReader读取本地文件
  7. java类的成员变量和局部变量的区别
  8. gitlab两种连接方式:ssh和http配置介绍
  9. 多线程生产者,消费者例题
  10. 5.数据中台 --- 数据汇聚联通:打破企业数据孤岛