也来说说C#异步委托 (转自 Rising_Sun)
前些日子,看到园子里面有人用老王喝茶的例子讲解了一下同步和异步,虽然没有代码实现,但是能够通俗易懂的讲解了同步、异步、阻塞、非阻塞的关系了,今天借题发挥,用一个热水器加热洗澡的例子来具体演示一下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类中有属性,设定温度,当前温度和一个水是否烧好的状态布尔值,并在烧水方法中让线程休眠5秒钟,其目的是符合实际情况,烧水总要有个时间过程。
下面我们的老王闪亮登场,老王有两个方法 分别是打开热水器和看电视,代码如下:
然后我们在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() 方法的时候,程序就会发生阻塞,一直在等待返回值,并没有立即执行老王看电视的方法,而是烧水方法完成后并返回当前水温数值之后,才会执行后面的代码。哈哈,这不就说明老王很傻,在烧水准备洗澡的时候,一直再傻傻的等待在热水器旁边,等水烧好了,再去看电视,然后再准备洗澡。这种情况就是我们说的同步阻塞。
那么这种情况如何解决呢?下面聪明的老刘登场了,老刘玩的就是异步,烧水的期间去看电视了,不用傻傻的等着了,代码如下:
我们在老刘类中主要 声明了个 委托 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());
}

代码运行结果如下:

至此,这个例子就演示完了,不足之处望大家多多指教!例子代码在此下载
也来说说C#异步委托 (转自 Rising_Sun)的更多相关文章
- C#固定时间执行指定事件(观察者模式+异步委托)
最近有个项目需要每天固定的时间去执行指定的事件,发现网上关于这样的文章比较少,而且比较散.通过学习了几篇文章后终于实现了这个功能,在此也特别感谢这些文章的作者们,这也是我第一次在园子里面发文章,望多指 ...
- c#线程之异步委托begininvoke、invoke、AsyncWaitHandle.WaitOne 、异步回调
单靠自己看书学总是会走很多弯路,任何人也不列外,有些时候自己遇到的很多问题,其它别人在很久之前也可能遇到过,上网查查可以走很大捷径,对自己的学习有很大帮助,刚开始弄线程这块,一开始只是看书,很多东西都 ...
- 异步委托(APM)使用Func异步操作,处理耗时操作
使用委托进行异步操作,处理一些耗时操作,防止主线程阻塞 使用例子: using System; using System.Collections.Generic; using System.Linq; ...
- C#: 异步委托
http://www.cnblogs.com/yingzhongwen/p/4568350.html 讲了委托与事件,但是对异步委托研究得还不够深入. http://www.cnblogs.com/l ...
- C#实现异步编程的两个简单机制(异步委托&定时器)及Thread实现多线程
创建线程的常用方法:异步委托.定时器.Thread类 理解程序.进程.线程三者之间的区别:简而言之,一个程序至少有一个进程,一个进程至少有一个线程进程就是在内存中运行的程序(即运行着的程序):一个进程 ...
- 异步委托 多线程实现摇奖器 winform版
using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using Sy ...
- Orchard 源码探索(Application_Start)之异步委托调用
2014年5月26日 10:26:31 晴 ASP.NET 接收到对应用程序中任何资源的第一个请求时,名为ApplicationManager 的类会创建一个应用程序域.应用程序域为全局变量提供应用程 ...
- c#并行任务多种优化方案分享(异步委托)
遇到一个多线程任务优化的问题,现在解决了,分享如下. 假设有四个任务: 任务1:登陆验证(CheckUser) 任务2:验证成功后从Web服务获取数据(GetDataFromWeb) 任务3:验证成功 ...
- 6.26学习 异步委托回调函数 VS 多线程 VS 并行处理
描述: 我现在是轮询着构建实例,然后这个实例去执行一个方法,但是执行方法需要大约10s时间,全部轮询下来需要很长时间.所以我现在要更改,头给了我两个方法,1多线程 2异步委托回调函数. 异步委托回调函 ...
- .net异步委托
委托Delegate是一个类,定义了方法的类型, 使得可以将方法当做另一个方法的参数来进行传递,这种将方法动态地赋给参数的做法,可以避免在程序中大佬使用If-Else(Switch)语句,同时使得程序 ...
随机推荐
- 使用pidstat查看进程资源使用情况
简介 pidstat主要用于监控全部或指定进程占用系统资源的情况,如CPU,内存.设备IO.任务切换.线程等.pidstat首次运行时显示自系统启动开始的各项统计信息,之后运行pidstat将显示自上 ...
- Treasure Exploration---poj2594(传递闭包Floyd+最小路径覆盖)
题目链接:http://poj.org/problem?id=2594 在外星上有n个点需要机器人去探险,有m条单向路径.问至少需要几个机器人才能遍历完所有的点,一个点可以被多个机器人经过(这就是和单 ...
- iOS邮箱、手机号等常用验证功能 判断字符串是否int float
手机号验证 /* 手机号码 13[0-9],14[5|7|9],15[0-3],15[5-9],17[0|1|3|5|6|8],18[0-9] 移动:134[0-8],13[5-9],147,15[0 ...
- YOGA Tablet 2 1371f 触屏失效,无声卡,蓝牙键盘都无法使用的解决办法
安装驱动! 下载地址 http://www.lenovocare.com.cn/Handler/Download.ashx?fileid=1234 安装后电源管理,声卡,触摸屏即可使用! 蓝牙键盘连接 ...
- 一个简单的3D范例,是在别人基础上面整理的。
一个简单的范例,是在别人基础上面整理的.原来的例子,框图太乱了,没有条理感. http://pan.baidu.com/s/1eQTyGCE
- 移动 H5(PC Web)前端性能优化指南
原文地址https://zhuanlan.zhihu.com/p/25176904?utm_source=wechat_session&utm_medium=social&utm_me ...
- 微博开源框架Motan初体验
前两天,我在开源中国的微信公众号看到新浪微博的轻量Rpc框架--Motan开源了.上网查了下,才得知这个Motan来头不小,支撑着新浪微博的千亿调用,曾经在2014年的春晚中有着千亿次的调用,对抗了春 ...
- 1.1 Getting Started-Core Concepts
一.Templates 使用Handlebars模板语言来描述程序的用户接口.每一个模板都有model的支持,如果model改变template就会自动更新. Expressions: li ...
- Good Bye 2018 Solution
A. New Year and the Christmas Ornament 签到. #include <bits/stdc++.h> using namespace std; int a ...
- 2018-2019 ACM-ICPC, Asia Seoul Regional Contest
ProblemA Circuits Solved. 题意: 有$n$个矩形,可以放两条平行与$x$轴的线,求怎么放置两条无线长的平行于$x$轴的线,使得他们与矩形相交个数最多 如果一个矩形同时与两条线 ...