C#多线程的异步委托/调用
C#异步调用(Asynchronou Delegate)
C#异步调用获取结果方法:主要有三种,也可以说是四种(官方说四种,电子书说三种),官方在MSDN上已经有详细的说明:链接
需要了解到获取异步执行的返回值,意味着你需要调用Delegate的BeginInvoke方法,而不是Invoke方法。
第一种就是书上没有说的,但是官方还是给出来的,就是通过调用EndInvoke方法来获取内容,查看如下代码:
class MyState
{
public int ThreadId = 0;
public int Data = 0; public MyState()
{ }
}
class AsyncInvoke
{
private MyState State = null; public AsyncInvoke()
{
State = new MyState();
} private int TakesAWhile(int data, int ms)
{
Console.WriteLine("TakesAWhile started");
Thread.Sleep(ms);
Console.WriteLine("TakesAWhile completed");
return ++data;
} public delegate int TakesAWhileDelegate(int data, int ms); public void RunEndInvoke()
{
TakesAWhileDelegate dl = new TakesAWhileDelegate(TakesAWhile);
IAsyncResult ar = dl.BeginInvoke(5, 3000, null, null);
int result = 0;
result = dl.EndInvoke(ar);
Console.WriteLine(string.Format("Result: {0}", result));
}
}
输出结果为:
TakesAWhile started
TakesAWhile completed
Result: 6
通过上面可以知道使用EndInvoke阻塞了主线程(RunEndInvoke函数),同时需要使用BeginInvoke的返回值ar作为EndInvoke的入参。
第二种方法Polling(轮询)BeginInvoke的返回值(IAsyncResult中的属性IsCompleted),将RunEndInvoke替换成下面的函数RunPolling:
public void RunPolling()
{
TakesAWhileDelegate dl = new TakesAWhileDelegate(TakesAWhile);
IAsyncResult ar = dl.BeginInvoke(5, 3000, null, null); while (ar.IsCompleted == false)
{
Thread.Sleep(500);
}
int result = 0;
result = dl.EndInvoke(ar);
Console.WriteLine(string.Format("Result: {0}", result));
}
结果同上,其实看到这里你也发现第二种跟第一种没有很大区别,所以书上没有阐明第一种方法,其实如果只是为了获取结果,用第一种方法就足够了。其实这两个例子没有很好的体现BeginInvoke的异步的优势,好像跟使用Invoke方法没有很大区别,都是事先了阻塞主线程,如下所示:
public void RunInvoke()
{
TakesAWhileDelegate dl = new TakesAWhileDelegate(TakesAWhile);
int result = dl.Invoke(5, 3000);
Console.WriteLine(string.Format("Result: {0}", result));
}
其实这就涉及的Invoke以及BeginInvoke的区别了,因为调用Invoke,会阻塞主线程,等待异步执行的完成(其实也没有什么异步的说法了)。不过调用BeginInvoke则回立即返回,但是异步执行的结果或者返回值如何获得, 就是这部分阐述的目的:获取异步执行的返回值或结果,所以你可以将上述的RunBeginInvoke替换成如下的:
public void RunEndInvoke()
{
TakesAWhileDelegate dl = new TakesAWhileDelegate(TakesAWhile);
IAsyncResult ar = dl.BeginInvoke(5, 3000, null, null);
// Do others in main thread.
Console.WriteLine("Do others in main thread...");
//
int result = 0;
result = dl.EndInvoke(ar);
Console.WriteLine(string.Format("Result: {0}", result));
}
这样输出结果如下所示:
Do others in main thread...
TakesAWhile started
TakesAWhile completed
Result: 6
这样你可以放别的工作在BeginInvoke的之后,在主线程执行,等到执行了,可以使用阻塞主线程来等待异步执行的结果来进行下一步的需要,这样情况能够部分提高工作效率,通常用于执行相互独立的模块,然后再等待两边结果(主线程以及异步线程的结果)来进行下一步的操作。
好了,讲第三种的方法来获取异步结果了,就是Wait Handle。 handle是通过BeginInvoke的返回值(ar)中的一个属性AsyncWaitHandle来获得的。一开始AsyncWaitHandle是没有初始化的,但是只要引用该属性,操作系统实现它,同时调用其waitone函数去等待异步执行完的信号。如果使用没有带参的waitone函数,则无限期阻塞主线程来等待异步执行,一旦执行完,则返回, 如下代码所示:
public void RunWaitHandle()
{
TakesAWhileDelegate dl = new TakesAWhileDelegate(TakesAWhile);
IAsyncResult ar = dl.BeginInvoke(5, 3000, null, null);
ar.AsyncWaitHandle.WaitOne();
Console.WriteLine("Wait for 3 seconds.");
int result = 0;
result = dl.EndInvoke(ar);
ar.AsyncWaitHandle.Close();
Console.WriteLine(string.Format("Result: {0}", result));
}
输出结果基本同上,就多了一行“Wait for 3 seconds."来识别是否等待了3秒钟,因为后面还有EndInvoke方法。
如果使用带参的,如waitone(1000, false)来判断当前返回值是true则意味着等待1秒,第二参数来判断是否退出同步。
从上面三种方法来看,的确是挺好的,但是每次都需要主线程去等待,大哥不好当,所以就有了第四种方法,就是使用BegingInvoke的第三个参数,该函数是在BeginInvoke调用的函数执行完后自动执行,不然也不叫做callback。
第四个参数通常将异步函数的delegate传递过去,这样在callback函数中就可以方便的获取到异步函数的执行结果或者返回值。
public void AsyncCompleted(IAsyncResult ar)
{
if (ar != null)
{
Console.WriteLine("After the async operation.");
TakesAWhileDelegate dl = ar.AsyncState as TakesAWhileDelegate;
int result = 0;
result = dl.EndInvoke(ar);
Console.WriteLine(string.Format("Result: {0}", result));
}
} public void RunAsyncCallback()
{
TakesAWhileDelegate dl = new TakesAWhileDelegate(TakesAWhile);
IAsyncResult ar = dl.BeginInvoke(5, 3000, AsyncCompleted, dl);
Console.WriteLine("Finish RunAsyncCallback.");
}
输出结果:
Finish RunAsyncCallback.
Press any key to continue...
TakesAWhile started
TakesAWhile completed
After the async operation.
Result: 6
现在Lambda很流行,虽然还是不是很懂,但是也赶下潮流,可以将上述的callback方法改写成lambda形式,同时可以不用指定关于BeginInvoke的第四个参数,同时可以在作用域中访问异步delegate(dl)。代码如下所示:
public void RunAsyncCallback2()
{
TakesAWhileDelegate dl = new TakesAWhileDelegate(TakesAWhile);
dl.BeginInvoke(5, 3000, ar => {
Console.Write("After async operation.");
int result = 0;
result = dl.EndInvoke(ar);
Console.WriteLine(string.Format("Result: {0}", result));
}, null);
Console.WriteLine("Finish RunAsyncCallback.");
}
至此,四种获取异步结果或者返回值的方法也说完了,现在补充一下别的知识,就是关于返回值的问题。
我们知道EndInvoke的调用或者Invoke的调用(这里抛开了异步的概念了)都只能返回一个值(真是废话!)。如果我们想在异步执行中修改多个值,可以选择的方法有: 使用一个结构体或者类(简言之就是一个复杂对象),将想要在异步程序中修改对象整合在一起,就如下面的代码所示:
private MyState TakesAWhile(int data)
{
Console.WriteLine("TakesAWhile started");
Thread.Sleep(3000);
Console.WriteLine("TakesAWhile completed");
return new MyState(){Data=++data, ThreadId=Thread.CurrentThread.ManagedThreadId};
} private delegate MyState TaskAWhileDelegate2(int data); public void RunEndInvokeWithStructReturn()
{
TaskAWhileDelegate2 dl = new TaskAWhileDelegate2(TakesAWhile);
IAsyncResult ar = dl.BeginInvoke(5, null, null);
MyState state = null;
state = dl.EndInvoke(ar); Console.WriteLine(string.Format("Data: {0}, ThreadId: {1}", state.Data, state.ThreadId));
}
输出结果为:
TakesAWhile started
TakesAWhile completed
Data: 6, ThreadId: 6
使用复杂结构体或者类能够实现多种参数的修改,一来节省输入参数,二来想怎么整合参数就怎么整合。
其实另一种实用的方法(个人认为,也是从C++一路走来,巴拉巴拉。。。),使用c#中的ref来代替指针的,指向已经分配好的复杂对象也是一个很好的方法,如下代码实现同上的功能:
private void TakesAWhile(int data, int ms, ref MyState state)
{
Console.WriteLine("TakesAWhile started");
Thread.Sleep(ms);
Console.WriteLine("TakesAWhile completed");
state.Data = ++data;
state.ThreadId = Thread.CurrentThread.ManagedThreadId;
} private delegate void TakesAWhileDelegate3(int data, int ms, ref MyState state); public void RunEndInvokeWithRefStruct()
{
TakesAWhileDelegate3 dl = new TakesAWhileDelegate3(TakesAWhile);
IAsyncResult ar = dl.BeginInvoke(5, 3000, ref State, null, null);
dl.EndInvoke(ref State, ar); // this State could be any other reference to MyState
Console.WriteLine(string.Format("Data: {0}, ThreadId: {1}", State.Data, State.ThreadId));
}
其中EndInvke调用中需要多给一个参数,如果这个参数是成员变量,给或者不给都不会影响到后面一句的打印情况,也就是state已经在异步函数中修改了,即使没有明显修改都没有问题。如果使用out作为输出参数,EndInvoke也需要额外的指定该参数,具体的实现就不给代码,因为实现没有什么好给的,而且本人不太好out这个传递,ref本命的人,希望能再别的地方寻找到使用out的有意义的用法,扯远了。
到此为止,四种方法都讲完了,异步函数的输入参数以及传递参数的方法以及技巧也讲述了,总结一下:
获取异步函数的结果或者返回值的方法有:
1. EndInvoke调用,阻塞主线程
2. Polling IAsyncResult中IsCompleted属性, 阻塞主线程,没有什么实际作用,浪费系统资源
3. Wait Handle响应,使用IAsyncResult中的AsyncWaitHandle属性去调用handle的waitone函数,阻塞主线程,使用重载带参函数实现是否等待同步或者直接跳出
4. 使用callback函数,并使其作为Begininvoke的第三个参数,使用异步delegate作为第四个参数,以使callback能够获取异步delegate的返回值。
使用心得:
如果需要跑两个相互独立的模块,或者主线程需要异步执行的结果或者返回值,可以使用前面三种方法;如果后续操作不太依赖于异步执行后的结果,可以使用callback,既能做到获取处理结果,又不会影响主线程。
PS:
上面讲述的主要是Thread或者task的异步调用BeginInvoke的结果获取方法,但是如果该异步函数中涉及到输出参数,就如刚才说的复杂对象中的成员是controller的话,就又有一章来讲了,对于UI以及后台线程的交互这是一个重要点,如果你不想你的UI经常假死的话,请收看下一回:Controller中BeginInvoke与Invoke的区别以及应用。
AD的优势是异步函数的强类型保证,参数的输入都是规定好的(delegate声明),如果需要传递参数可以在函数中使用ref关键字(只针对引用类型,值类型无效果)。由于异步调用时使用task底层实现的,所以他具有了task的优势,方便使用,但是也有其缺点,不能够想thread那样提供丰富的对线程设置(后台,优先级,不能中断)。
C#多线程的异步委托/调用的更多相关文章
- Orchard 源码探索(Application_Start)之异步委托调用
2014年5月26日 10:26:31 晴 ASP.NET 接收到对应用程序中任何资源的第一个请求时,名为ApplicationManager 的类会创建一个应用程序域.应用程序域为全局变量提供应用程 ...
- c# 多线程与异步调用
异步操作的本质 在方法调用前为异步方法指定一个回调函数,方法调用后被线程池中的一个线程接管,执行该方法.主线程立即返回,继续执行其他工作或响应用户请求.如果异步方法执行完 毕,回调函数被自动执行,以处 ...
- 6.26学习 异步委托回调函数 VS 多线程 VS 并行处理
描述: 我现在是轮询着构建实例,然后这个实例去执行一个方法,但是执行方法需要大约10s时间,全部轮询下来需要很长时间.所以我现在要更改,头给了我两个方法,1多线程 2异步委托回调函数. 异步委托回调函 ...
- C# 多线程操作之异步委托
标签: 多线程任务nullstringhtml工作 2012-06-29 23:00 1276人阅读 评论(0) 收藏 举报 分类: C/C++/C#/dotnet(126) 目录(?)[+] ...
- C#实现异步编程的两个简单机制(异步委托&定时器)及Thread实现多线程
创建线程的常用方法:异步委托.定时器.Thread类 理解程序.进程.线程三者之间的区别:简而言之,一个程序至少有一个进程,一个进程至少有一个线程进程就是在内存中运行的程序(即运行着的程序):一个进程 ...
- 异步委托 多线程实现摇奖器 winform版
using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using Sy ...
- C#多线程编程之:异步事件调用
当一个事件被触发时,订阅该事件的方法将在触发该事件的线程中执行.也就是说,订阅该事件的方法在触发事件的线程中同步执行.由此,存在一个问 题:如果订阅事件的方法执行时间很长,触发事件的线程被阻塞,长时间 ...
- c# 中的多线程和异步
前言: 1.异步和多线程有区别吗? 答案:多线程可以说是实现异步的一种方法方法,两者的共同目的:使主线程保持对用户操作的实时响应,如点击.拖拽.输入字符等.使主程序看起来实时都保持着等待用户响应的状态 ...
- 第一章 管理程序流(In .net4.5) 之 实现多线程和异步处理
1. 概述 本章主要讲解.net4.5如何实现多线程和异步处理的相关内容. 2. 主要内容 2.1 理解线程 ① 使用Thread类 public static class Program { ...
随机推荐
- web.py学习心得
1.注意判断数字时,如果是get传递的参数,一定要用int转换.不然出错. 2.$var 定义时,冒号后的内容不是python内容,需加上$符号.如$var naviId:$naviId. 3.各个模 ...
- Linux基础之常用命令(1)
一 linux命令的格式 1.命令 [选项] [参数] ls list 显示目录下内容 ① 命令名称:ls 命令英文原意:list 命令所在路径:/bin/ls 执行权限:所有用户 功能 ...
- [DFNews] Cellebrite UFED系列更新, 支持IOS7
10月15日,Cellebrite公司对旗下产品进行了更新,包括UFED Classic.UFED Touch.Physical Analyzer.Logical Analyzer.Phone Det ...
- 用javascript比较快速排序和合并排序的优劣
<script> //用来调用排列方法的类 function arr_sort(arr){ var startTime,endTime; var priv_arr = new Array; ...
- Nopcommerce 二次开发0
Nopcommerce 是国外的一个高质量的开源b2c网站系统,基于EntityFramework6.0和MVC5.0,使用Razor模板引擎,有很强的插件机制,包括支付配送功能都是通过插件来实现的 ...
- Java应用程序访问网络资源--HttpClient
HttpClient的最本质的功能是执行HTTP方法.一个HTTP方法的执行涉及到一个或几个HTTP请求/ HTTP响应的交流,通常由HttpClient的内部处理.用户预计将提供一个请求对象来执行和 ...
- 【转】伪静态URLRewrite学习笔记
UrlRewrite: UrlRewrite就是我们通常说的地址重写,用户得到的全部都是经过处理后的URL地址,类似于Apache的mod_rewrite.将我们的动态网页地址转化为静态的地址,如ht ...
- LNK1123: 转换到 COFF 期间失败: 文件无效或损坏
连接器LNK是通过调用cvtres.exe完成文件向coff格式的转换的,所以出现这种错误的原因就是cvtres.exe出现了问题. 在电脑里面搜索一下cvtres.exe,发现存在多个文件,使用最新 ...
- js对象常用2中构造方法
//js 对象的构造方法通常有2中情况: //第一种是通过json对象构造 var persion={ name:"孙悟空", age:40, eat:function () { ...
- cacti应用
cacti被很多IDC/CDN提供商用来进行带宽计算使用:带宽的95计费(95th Percentile charging) 95计费法是CDN常用计费方法: CDN基本上是每月结一次款.每5分钟取一 ...