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,代码如下:
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());
}
代码运行结果如下:
C#使用委托进行异步编程。的更多相关文章
- Asynchronous Programming Using Delegates使用委托进行异步编程
http://msdn.microsoft.com/zh-cn/library/22t547yb(v=vs.110).aspx https://github.com/chucklu/Test/tree ...
- c# 基于委托的异步编程模型(APM)测试用例
很多时候,我们需要程序在执行某个操作完成时,我们能够知道,以便进行下一步操作. 但是在使用原生线程或者线程池进行异步编程,没有一个内建的机制让你知道操作什么时候完成,为了克服这些限制,基于委托的异步编 ...
- 【温故知新】c#异步编程模型(APM)--使用委托进行异步编程
当我们用到C#类许多耗时的函数XXX时,总会存在同名的类似BeginXXX,EndXXX这样的函数. 例如Stream抽象类的Read函数就有 public abstract int Read(byt ...
- 委托的异步编程和同步编程的使用( Invoke 和BeginInvoke)
一,区别: 使用Invoke完成一个委托方法的封送,就类似于使用SendMessage方法来给界面线程发送消息,是一个同步方法.也就是说在Invoke封送的方法被执行完毕前,Invoke方法不会返回, ...
- C#多线程和异步(三)——一些异步编程模式
一.任务并行库 任务并行库(Task Parallel Library)是BCL中的一个类库,极大地简化了并行编程,Parallel常用的方法有For/ForEach/Invoke三个静态方法.在C# ...
- .NET “底层”异步编程模式——异步编程模型(Asynchronous Programming Model,APM)
本文内容 异步编程类型 异步编程模型(APM) 参考资料 首先澄清,异步编程模式(Asynchronous Programming Patterns)与异步编程模型(Asynchronous Prog ...
- C#秘密武器之异步编程
一.概述 1.什么是异步? 异步操作通常用于执行完成时间可能较长的任务,如打开大文件.连接远程计算机或查询数据库.异步操作在主应用程序线程以外的线程中执行.应用程序调用方法异步执行某个操作时,应用程序 ...
- (41)C#异步编程
VS2010是经常阻塞UI线程的应用程序之一.例如用vs2010打开一个包含数百个项目的解决方案,可以要等待很长时间(感觉像卡死),自从vs2012情况得到了改善,项目在后台进行了异步加载. 一.同步 ...
- C#中委托实现的异步编程
所谓同步:如果在代码中调用了一个方法,则必须等待该方法所有的代码执行完毕之后,才能回到原来的地方执行下一行代码. 异步:如果不等待调用的方法执行完,就执行下一行代码. 1.0 同步例子: class ...
随机推荐
- Putty文件夹蓝色太暗问题
Putty文件夹蓝色太暗问题 用Putty通过ssh登陆Linux服务器时,有时候会发现系统默认的蓝色字体太暗,具体解决方法如下: (1)打开putty客户端,选择某登陆Session,然后load: ...
- codevs1004四子连棋[BFS 哈希]
1004 四子连棋 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 黄金 Gold 题目描述 Description 在一个4*4的棋盘上摆放了14颗棋子,其中有7颗 ...
- TestLink学习二:Windows搭建TestLink环境
环境准备: 搭建php5.4.39+apache2.2+mysq5.5.28l环境 (可参考http://www.cnblogs.com/yangxia-test/p/4414161.html) (注 ...
- MVC 表单提交【转】
[转自]:http://www.cnblogs.com/dengdl/archive/2011/07/14/2106849.html 在做Asp.Net MVC项目中,都知道View负责页面展示数据或 ...
- webpack中output配置项中chunkFilename属性的用法
chunkFilename和webpack.optimize.CommonsChunkPlugin插件的作用差不多,都是用来将公共模块提取出来,但是用法不一样,这里主要介绍chunkFilename的 ...
- swift中第三方网络请求库Alamofire的安装与使用
swift中第三方网络请求库Alamofire的安装与使用 Alamofire是swift中一个比较流行的网络请求库:https://github.com/Alamofire/Alamofire.下面 ...
- asp.net core 日志
日志输出是应用程序必不可少的部分,log4net,nlog这些成熟的组件在之前的项目中被广泛使用,在asp.net core的项目中没有找到与之对应的log4net版本,nlog对core提供了很好的 ...
- Linux中 groupadd 和 useradd 的命令说明
groupadd [options] group 说明The groupadd command creates a new group account using the values specifi ...
- 解决 docker on windows下网络不通
问题:公司有一台闭置的windows服务器,于是想利用起来,但是在启动容器后始终无法通信成功. 研究: 1. 发现安装包中包含virtualbox, 于是怀疑windows下的docker是在virt ...
- 基于ASP.NET MVC的热插拔模块式开发框架(OrchardNoCMS)--BootStrap
按照几个月之前的计划,也应该写一个使用Bootstrap作为OrchardNoCMS的UI库.之前这段时间都是在学习IOS开发,没顾得上写,也没顾得上维护OrchardNoCMS代码.看看我的活动轨迹 ...