C# 委托高级应用----线程——创建无阻塞的异步调用(二)
了解IAsyncResult
现在我们已经了解,EndInvoke可以给我们提供传出参数与更新后的ref参数;也可以向我们导出异步函数中的异常信息。例如,我们使用BeginInvoke调用了异步函数Sleep,它开始执行。之后调用EndInvoke,可以获取Sleep何时执行完成。但如果我们在Sleep执行完成20分钟后,才去调用EndInvoke呢?EndInvoke仍然会给我们提供传出值及异步中的异常(假如产生了异常),那么这些信息到底存储在哪里?EndInvoke如何在函数执行如此久之后仍然能够调用这些返回值?答案就在于IAsyncResult对象。EndInvoke每次在执行后,都会调用一个该对象作为参数,它包括以下信息:
● 异步函数是否已经完成
● 对调用了BeginInvoke方法的委托的引用
● 所有的传出参数及它们的值
● 所有的ref参数及它们的更新值
● 函数的返回值
● 异步函数产生的异常
IAsyncResult看起来空无一物,这是因为它仅仅是一个包含了若干属性的接口;而实际上,它是一个System.Runtime.Remoting.Messaging.AsyncResult对象。
如果我们在编译器运行期间监视tag的状态,就会发现,AsyncResult对象下包含类型为System.Runtime.Remoting.Messaging.ReturnMessage的对象。点开它,就会发现这个标签中包含的所有的异步函数的执行信息!
使用Callback委托:好莱坞原则”不要联系我,我会联系你”
目前为止,我们需要了解如何传递参数、如何捕捉异常;了解我们的异步方法其实是执行在线程池中的某个具体线程对象中。唯一未涉及到的就是如何在异步函数执行完成后得到通知。毕竟,阻塞调用线程等待函数结束的做法始终差强人意。为了实现这个目的,我们必须为BeginInvoke函数提供一个Callback委托。观察一下两个函数:
class Program
{
public delegate string DelegateWithParameters(out int param1, string param2, ref ArrayList param3);
static private string FuncWithParameters(out int param1, string param2, ref ArrayList param3)
{
// 我们在这里改变参数值
param1 = ;
param2 = "hello";
param3 = new ArrayList(); return "thank you for reading me";
}
static private void CallSleepWithoutOutAndRefParameterWithCallback()
{ } private static void CallBack(IAsyncResult tag)
{
// 我们的int参数标记了out,因此此处不能定义初始值
int intOutputValue;
ArrayList list = null; // IAsyncResult实际上就是AsyncResult对象,
// 取得它也就可以从中取得用于调用函数的委托对象
AsyncResult result = (AsyncResult)tag; // 取得委托
DelegateWithParameters del = (DelegateWithParameters)result.AsyncDelegate; // 取得委托后,我们需要在其上执行EndInvoke。
// 这样就可以取得函数中的执行结果。
string strReturnValue = del.EndInvoke(out intOutputValue, ref list, tag); Console.WriteLine(intOutputValue);
Console.WriteLine(list.Count);
Console.WriteLine(tag);
Console.WriteLine(strReturnValue);
} public static void Main(string[] args)
{
// 创建几个参数
string strParam = "Param1";
int intValue = ;
ArrayList list = new ArrayList();
list.Add("Item1"); // 创建委托对象
DelegateWithParameters delSleep =
new DelegateWithParameters(FuncWithParameters); delSleep.BeginInvoke(out intValue, strParam, ref list, new AsyncCallback(CallBack), null);
Console.ReadLine();
}
} 0
System.Runtime.Remoting.Messaging.AsyncResult
thank you for reading me
在这里,我们向BeginInvoke传递了Callback回调函数。这样.NET就可以在FuncWithParameters()执行完后调用Callback函数。在之前,我们已经了解到,必须使用EndInvoke来取得函数的执行结果,注意上面为了使用EndInvoke,我们使用了一些特殊操作来取得delegate对象。
// IAsyncResult实际上就是AsyncResult对象,
// 取得它也就可以从中取得用于调用函数的委托对象
AsyncResult result = (AsyncResult)tag; // 取得委托
DelegateWithParameters del = (DelegateWithParameters)result.AsyncDelegate;
最后一个问题:回调函数执行在什么线程?
总而言之,Callback函数(回调函数)是.NET通过我们的委托对象来实现调用的。我们可能会希望得到一个更清晰的画面:回调函数究竟执行在那个线程?为了达到这个目的:我们在函数中加入线程日
class Program
{
public delegate string DelegateWithParameters(out int param1, string param2, ref ArrayList param3);
static private string FuncWithParameters(out int param1, string param2, ref ArrayList param3)
{
//// 记录线程信息
//Console.WriteLine("In FuncWithParameters: Thread Pool? "
// + Thread.CurrentThread.IsThreadPoolThread.ToString() +
// " Thread Id: " + Thread.CurrentThread.GetHashCode()); // 挂起秒以模拟线程在这里执行了耗时较长的任务
Thread.Sleep(); // 我们在这里改变参数值
param1 = ;
param2 = "hello";
param3 = new ArrayList(); // 这里执行一些耗时较长的工作
// Thread.Sleep(3000); return "thank you for reading me";
}
static private void CallSleepWithoutOutAndRefParameterWithCallback()
{ } private static void CallBack(IAsyncResult tag)
{
// 回调函数在什么线程执行?
Console.WriteLine("In Callback: Thread Pool? "
+ Thread.CurrentThread.IsThreadPoolThread.ToString() +
" Thread Id: " + Thread.CurrentThread.GetHashCode()); // 我们的int参数标记了out,因此此处不能定义初始值
int intOutputValue;
ArrayList list = null; // IAsyncResult实际上就是AsyncResult对象,
// 取得它也就可以从中取得用于调用函数的委托对象
AsyncResult result = (AsyncResult)tag; // 取得委托
DelegateWithParameters del = (DelegateWithParameters)result.AsyncDelegate; // 取得委托后,我们需要在其上执行EndInvoke。
// 这样就可以取得函数中的执行结果。
string strReturnValue = del.EndInvoke(out intOutputValue, ref list, tag); string strState = (string)tag.AsyncState; Console.WriteLine("EndInvoke的传递参数" + strState);
Console.WriteLine(intOutputValue);
Console.WriteLine(list.Count);
Console.WriteLine(strReturnValue);
} public static void Main(string[] args)
{
// 创建几个参数
string strParam = "Param1";
int intValue = ;
ArrayList list = new ArrayList();
list.Add("Item1"); // 创建委托对象
DelegateWithParameters delSleep =
new DelegateWithParameters(FuncWithParameters);
for (int i = ; i < ; i++)
{
delSleep.BeginInvoke(out intValue, strParam, ref list, new AsyncCallback(CallBack), "第" + i + "次");
}
Console.ReadLine();
}
}
In Callback: Thread Pool? True Thread Id:
EndInvoke的传递参数第1次 thank you for reading me
In Callback: Thread Pool? True Thread Id:
EndInvoke的传递参数第0次 thank you for reading me
注意FuncWithParameter函数被连续执行了3次,它们依次被执行在相互独立的线程上,并且这些线程来自于线程池。而他们各自的回调函数也执行在与FuncWithParameter相同的线程中。线程11执行了FuncWithParameter,3秒后,它的回调函数也执行在线程11中,线程12、13也是同样。这样,我们可以认为回调函数实际上是异步函数的一种延续。
为什么要这样做?也许是因为这样我们就不必过多的耗费线程池中的线程,达到线程复用的效果;通过执行在相同的线程,也可以避免不同的线程间传递上下文环境带来的损耗问题。
到此为止,我们在Form中执行异步函数,将会得到一个完全不堵塞主线程的异步调用,这就是我们所希望的效果!
应用场景模拟
现在我们了解了BeginInvoke、EndInvoke、Callback的使用及特点,如何将他们运用到我们的Win Form程序中,使数据的获取不再阻塞UI线程,实现异步加载数据的效果?我们现在通过一个具体实例来加以说明。
场景描述:将系统的操作日志从数据库中查询出来,并且加载到前端的ListBox控件中。
要求:查询数据库的过程是个时间复杂度较高的作业,但我们的窗体在执行查询时,不允许出现”假死”的情况。
private void button1_Click(object sender, EventArgs e)
{
GetLogDelegate getLogDel = new GetLogDelegate(GetLogs); getLogDel.BeginInvoke(new AsyncCallback(LogTableCallBack), null);
} public delegate DataTable GetLogDelegate(); /// <summary>
/// 从数据库中获取操作日志,该操作耗费时间较长,
/// 且返回数据量较大,日志记录可能超过万条。
/// </summary>
/// <returns></returns>
private DataTable GetLogs()
{
string sql = "select * from ***";
DataSet ds = new DataSet(); using (OracleConnection cn = new OracleConnection(connectionString))
{
cn.Open(); OracleCommand cmd = new OracleCommand(sql, cn); OracleDataAdapter adapter = new OracleDataAdapter(cmd);
adapter.Fill(ds);
} return ds.Tables[];
} /// <summary>
/// 绑定日志到ListBox控件。
/// </summary>
/// <param name="tag"></param>
private void LogTableCallBack(IAsyncResult tag)
{
AsyncResult result = (AsyncResult)tag;
GetLogDelegate del = (GetLogDelegate)result.AsyncDelegate; DataTable logTable = del.EndInvoke(tag); if (this.listBox1.InvokeRequired)
{
this.listBox1.Invoke(new MethodInvoker(delegate()
{
BindLog(logTable);
}));
}
else
{
BindLog(logTable);
}
} private void BindLog(DataTable logTable)
{
this.listBox1.DataSource = logTable;
}
以上代码在获取数据时,将不会带来任何UI线程的阻塞。
总结:
写下本文的主要目的在于总结以非阻塞模式调用函数的方法,我们应当了解以下结论;
● Delegate会对BeginInvoke与EndInvoke的调用生成正确的参数,所有的传出参数、返回值与异常都可以在EndInvoke中取得。
● 不要忘记BeginInvoke是取自线程池中的线程,要注意防止异步任务的数量超过了线程池的线程上限值。
● CallBack委托表示对与异步任务的回调,它将使我们从阻塞的困扰中彻底解脱。
● 截止到目前为止,UI线程在处理异步工作时将不再阻塞,而只有在更新UI具体内容时才会发生阻塞。
C# 委托高级应用----线程——创建无阻塞的异步调用(二)的更多相关文章
- C# 委托高级应用----线程——创建无阻塞的异步调用(一)
前言 本文大部分内容来自于mikeperetz的Asynchronous Method Invocation及本人的一些个人体会所得,希望对你有所帮助.原英文文献可以在codeproject中搜索到. ...
- 谈.Net委托与线程——创建无阻塞的异步调用(二)
了解IAsyncResult 现在我们已经了解,EndInvoke可以给我们提供传出参数与更新后的ref参数:也可以向我们导出异步函数中的异常信息.例如,我们使用BeginInvoke调用了异步函数S ...
- 谈.Net委托与线程——创建无阻塞的异步调用(一)
前言 本文大部分内容来自于mikeperetz的Asynchronous Method Invocation及本人的一些个人体会所得,希望对你有所帮助.原英文文献可以在codeproject中搜索到. ...
- Python并发编程06 /阻塞、异步调用/同步调用、异步回调函数、线程queue、事件event、协程
Python并发编程06 /阻塞.异步调用/同步调用.异步回调函数.线程queue.事件event.协程 目录 Python并发编程06 /阻塞.异步调用/同步调用.异步回调函数.线程queue.事件 ...
- Unix 环境高级编程---线程创建、同步、
一下代码主要实现了linux下线程创建的基本方法,这些都是使用默认属性的.以后有机会再探讨自定义属性的情况.主要是为了练习三种基本的线程同步方法:互斥.读写锁以及条件变量. #include < ...
- 并发编程 - 线程 - 1.线程queue/2.线程池进程池/3.异步调用与回调机制
1.线程queue :会有锁 q=queue.Queue(3) q.get() q.put() 先进先出 队列后进先出 堆栈优先级队列 """先进先出 队列"& ...
- C# 创建线程的简单方式:异步委托 .
定义一个委托调用的方法:TakesAWhile //定义委托要引用的方法 private static int TakesAWhile(int data, int ms) { Console.Writ ...
- 理解Android线程创建流程(转)
/android/libcore/libart/src/main/java/java/lang/Thread.java /art/runtime/native/java_lang_Thread.cc ...
- 理解Android线程创建流程
copy from : http://gityuan.com/2016/09/24/android-thread/ 基于Android 6.0源码剖析,分析Android线程的创建过程 /androi ...
随机推荐
- node学习笔记(二)(ajax方式向node后台提交数据)
通过ajax向node后台提交数据过程(附手写前后台代码),并总结post与get的区别 POST 前台代码 //CSS简单给点样式 <style> form{ width: 200px; ...
- C# winform 程序开发知识点总结(干货)
1.数据库连接及操作 在说数据库操作之前,先说一下数据库连接操作字符串的获取 首先,点击服务器资源管理器,接下来选中数据连接右键点击添加连接,填入你要连接的服务器名称,点击单选框使用SQL Serve ...
- K-means 算法
本学习笔记参考自吴恩达老师机器学习公开课 聚类算法是一种无监督学习算法.k均值算法是其中应用最为广泛的一种,算法接受一个未标记的数据集,然后将数据聚类成不同的组.K均值是一个迭代算法,假设我们想要将数 ...
- Dashboard集群
#Dashboard集群 openstack pike 部署 目录汇总 http://www.cnblogs.com/elvi/p/7613861.html ##.Dashboard集群 #####在 ...
- 《Clean Code》 代码简洁之道
作者介绍 原文作者: Robert C. Martin, Object Mentor公司总裁,面向对象设计.模式.UML.敏捷方法学和极限编程领域的资深顾问,是<敏捷软件开发:原则.模式.与实践 ...
- 【译】使用Jwt身份认证保护 Asp.Net Core Web Api
原文出自Rui Figueiredo的博客,原文链接<Secure a Web Api in ASP.NET Core> 摘要:这边文章阐述了如何使用 Json Web Token (Jw ...
- JavaScript实现AOP(面向切面编程)
什么是AOP? AOP(面向切面编程)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括日志统计.安全控制.异常处理等.把这些功能抽离出来之后, 再通过" ...
- 前端备忘录--JQuery选择器
基本选择器 基本选择器是最常用的选择器,也是最简单的选择器. $("#test") //选取id为test的元素 $(".test") //选取class为te ...
- Linux上跑两个版本的php,5.4.45和5.3.24
首先,装的实在艰难,所以容我吐槽两句: PHP好个P,两个小版本都不兼容,把这个php5.3.24放在5.4.45环境下都跑不了.对于我这种用多了向下兼容的java的人来说,简直无法想象! 网上有装俩 ...
- 命令行保存指定目录文件的名字(可包含文件夹文字)到txt文本文件
Microsoft Visual Studio中配置OpenCV解决方案属性的时候, 需要将OpenCV的lib扩展名的库文件添加到属性的依赖列表里面,网上的有些人博客里面直接给出的会有问题(但大多数 ...