c#进行HTTP协议的本地APIjson接口怎么调用的调用,并且正确返回JSON数据,要详细代码及注释谢谢。739807792

淘宝开放平台 - 文档中心
API调用方法详解
开放平台(TOP)API 调用为HTTP 方式,开发者可以按一定格式自行拼装HTTP请求进行API 调用,也可以基于我们提供SDK(SDK是由程序自动生成的代码包,包含了请求、加密生成sign等一些必要的功能,使用SDK进行调用非常简单,建议使用)进行API调用。以下内容主要为说明具体调用原理,具体可参考实例代码 。实例代码:SDK下载:
一、调用介绍
TOP作为淘宝数据插槽,只要用户按照TOP的规范拼装一个正确的URL,通过HTTP请求到TOP,就能够拿到用户自己需要的数据。调用原理示意图如下(注:淘宝 API &接口&采用 REST 风格,只需将所需参数拼装成http请求,即可调用。故支持 http 协议请求的程序语言,均可调用淘宝API,例如php、C#、asp、java、delphi
二、调用入口
调用入口即调用接口(API)的请求访问该地址。开放平台提供了如下两类入口网关,开发者选择其中任意一种即可。&& 其中正式环境对应淘宝线上数据,沙箱则是对应沙箱数据,两者分开,相应的对应的AppKey、AppSecret 等参数也各自不同。沙箱环境测试使用沙箱环境的入口网关、AppKey、AppSecret及沙箱的测试账号等;正式环境测试则直接用正式环境的入口网关、AppKey、AppSecret及淘宝账号。有关沙箱和正式环境的更多说明,可参考:
1、http 网关
正式环境:http://gw./router/rest&&&&& 沙箱环境:http://gw./router/rest
2、https 网关
正式环境:/router/rest&&&& 沙箱环境:https://gw./router/rest&
三、调用参数
调用API ,必须传入系统参数和应用参数。系统参数详细介绍如下;应用参数则根据不同API 具体入参,更多请参考。这里以调用为例说明(通过API 文档可看到,调用该API应用参数仅 fields)
1、系统参数
API接口名称
时间戳,格式为yyyy-mm-dd HH:mm:ss,例如: 13:52:03。淘宝API服务端允许客户端请求时间误差为6分钟。
可选,指定响应格式。默认xml,目前支持格式为xml,json
TOP分配给应用的AppKey ,创建应用时可获得
API协议版本,可选值:2.0。
对 API 调用参数(除sign外)进行 md5 加密获得。获取方法参考如下 3、签名sign
sign_method
参数的加密方法选择,可选值是:md5,hmac
1、通过授权(参考:)得到的Access Token值(原老的TOP协议对应为SessionKey,现Oauth2.0协议对应为Access Token)。2、是否传人该值,根据调用的API 判断。API文档上 API用户授权类型 标识为“需要”的,调用时该字段必传;标识为“不需要”的,该值可不传人。
2、应用参数
设置返回字段,如需获取卖家昵称,则传入nick 。更多请参考API文档说明
3、签名sign
调用API 时需要对请求参数进行签名验证,TOP服务器也会对该请求参数进行验证是否合法的。方法如下:&&&&& 根据参数名称(除签名和图片)将所有请求参数按照字母先后顺序排序:key + value .... key + value&&&&& 例如:将foo=1,bar=2,baz=3 排序为bar=2,baz=3,foo=1,参数名和参数值链接后,得到拼装字符串bar2baz3foo1&&&&& 系统同时支持MD5和HMAC两种加密方式:&&&&& md5:将secret 拼接到参数字符串头、尾进行md5加密后,再转化成大写,格式是:byte2hex(md5(secretkey1value1key2value2...secret))&&&&& hmac:采用hmac的md5方式,secret只在头部的签名后再转化成大写,格式 是:byte2hex (hmac(key1value1key2value2..., secret))&&&&& 注:hex为自定义方法,JAVA中MD5是对字节数组加密,加密结果是16字节,我们需要的是32位的大写字符串,图片参数不用加入签名中测
试 工具使用的是HMAC的加密方式。
四、调用示例
调用API:,应用参数fields 返回nick& ,使用系统默认MD5加密,因为各语言语法不一致,以下实例只体现逻辑。为便于说明,假设 app_key、secret、session 值均为 test& 。
&1、输入参数为
method=taobao.user.seller.get&&&&&& timestamp= 13:52:03&&&&&& format=xml&&&&&& app_key=test&&&&&& v=2.0&&&&&& fields=nick&&&&&& sign_method=md5&&&&&& session=test
&2、按首字母升序排列
app_key=test&&&&&& fields=nick&&&&&& format=xml&&&&&& method=taobao.user.seller.get&&&&&& session=test&&&&&& sign_method=md5&&&&&& timestamp= 13:52:03&&&&&& v=2.0
&3、连接字符串
连接参数名与参数值,并在首尾加上secret,如下:&&&&&& testapp_keytestfieldsnickformatxmlmethodtaobao.user.seller.getsessiontestsign_methodmd5timestamp 13:52:03v2.0test&&
&4、生成签名 sign
32位大写MD5值-&72CB4D809B375AD879C64
&5、拼装API请求
将所有参数值转换为UTF-8编码,然后拼装,通过浏览器访问该地址,即成功调用一次接口,如下(http或https网关):&&&&& http://gw./router/rest?sign=72CB4D809B375AD879C64&timestamp=+13%3A52%&&&&&&&&&& &&&&&& 3A03&v=2.0&app_key=test&method=taobao.user.seller.get&format=xml&session=test&fields=nick&&&&& /router/rest?sign=72CB4D809B375AD879C64&timestamp=+13%3A52%&&&&&&&&& &&&&&& 3A03&v=2.0&app_key=test&method=taobao.user.seller.get&format=xml&session=test&fields=nick
五、注意事项
&1、所有的请求和响应数据编码皆为utf-8格式,url里的所有参数值请做urlencode编码。如果请求的Content-Type是 application/x-www-form-&&&&&& urlencoded, http body里的所有参数值也做urlencode编码;如果是multipart/form-data格式,每个表单字段的参数值无需编码,但每个表单&&&&&& 字段的charset部分需要指定为utf-8&&&&&& 2、商品等公开信息查询类API建议用get请求,交易等隐私信息查询和修改类API建议用post请求&&&&&& 3、taobao.user.seller.get 为获取卖家用户信息接口,如果不是卖家身份,可用获取买家用户信息接口 taobao.user.buyer.get 进行测试。&&&&&& 4、如需在沙箱环境测试,请在应用沙箱管理页面获取沙箱环境对应的appkey 和appsecet ,对应的session值 也用沙箱帐号登录授权获得,沙&&&&&& 箱授权和正式环境授权类似,详细可参考上一节 用户授权介绍& &&&&&&& 5、生成签名(sign)仅是未使用SDK进行API调用时需要操作;如使用SDK,该步骤SDK会自动完成。&速卖通的API调用,调试返回400错误,但谷歌浏览器却能看到Json数据
[问题点数:100分,结帖人zhang1peng2008]
速卖通的API调用,调试返回400错误,但谷歌浏览器却能看到Json数据
[问题点数:100分,结帖人zhang1peng2008]
不显示删除回复
显示所有回复
显示星级回复
显示得分回复
只显示楼主
相关帖子推荐:
2014年2月 .NET技术大版内专家分月排行榜第二
2014年4月 .NET技术大版内专家分月排行榜第三2014年3月 .NET技术大版内专家分月排行榜第三2013年10月 .NET技术大版内专家分月排行榜第三
2014年2月 .NET技术大版内专家分月排行榜第二
2014年4月 .NET技术大版内专家分月排行榜第三2014年3月 .NET技术大版内专家分月排行榜第三2013年10月 .NET技术大版内专家分月排行榜第三
匿名用户不能发表回复!|
每天回帖即可获得10分可用分!小技巧:
你还可以输入10000个字符
(Ctrl+Enter)
请遵守CSDN,不得违反国家法律法规。
转载文章请注明出自“CSDN(www.csdn.net)”。如是商业用途请联系原作者。IOS中Json解析的四种方法 - 集结号
- 博客频道 - CSDN.NET
123893人阅读
作为一种轻量级的数据交换格式,json正在逐步取代xml,成为网络数据的通用格式。
有的json代码格式比较混乱,可以使用此“/”网站来进行JSON格式化校验()。此网站不仅可以检测Json代码中的错误,而且可以以视图形式显示json中的数据内容,很是方便。
从IOS5开始,APPLE提供了对json的原生支持(NSJSONSerialization),但是为了兼容以前的ios版本,可以使用第三方库来解析Json。
本文将介绍TouchJson、 SBJson 、JSONKit 和 iOS5所支持的原生的json方法,解析国家气象局API,TouchJson和SBJson需要下载他们的库
TouchJson包下载:&
SBJson 包下载:&
国家气象局提供的天气预报接口
接口地址有三个:
http://.cn/data/sk/.html
http://.cn/data/cityinfo/.html
http://.cn/data/.html
第三接口信息较为详细,提供的是6天的天气,关于API所返回的信息请见,全国各城市对应这一个id号,根据改变id好我们就可以解析出来各个城市对应天气;
下面介绍四种方法解析JSON:
首先建立一个新的工程,(注意不要选择ARC机制)添加如下控件:
如上图所示。下面展出程序代码:
文件 ViewController.h 中:
#import &UIKit/UIKit.h&
@interface ViewController : UIViewController
@property (retain, nonatomic) IBOutlet UITextView *txtV
- (IBAction)btnPressTouchJson:(id)
- (IBAction)btnPressSBJson:(id)
- (IBAction)btnPressIOS5Json:(id)
- (IBAction)btnPressJsonKit:(id)
&@end文件ViewController.m中主要代码:
(1)使用TouchJSon解析方法:(需导入包:#import &TouchJson/JSON/CJSONDeserializer.h&)
//使用TouchJson来解析北京的天气
- (IBAction)btnPressTouchJson:(id)sender {
//获取API接口
NSURL *url = [NSURL URLWithString:@&.cn/data/.html&];
//定义一个NSError对象,用于捕获错误信息
NSString *jsonString = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&error];
NSLog(@&jsonString---&%@&,jsonString);
//将解析得到的内容存放字典中,编码格式为UTF8,防止取值的时候发生乱码
NSDictionary *rootDic = [[CJSONDeserializer deserializer] deserialize:[jsonString dataUsingEncoding:NSUTF8StringEncoding] error:&error];
//因为返回的Json文件有两层,去第二层内容放到字典中去
NSDictionary *weatherInfo = [rootDic objectForKey:@&weatherinfo&];
NSLog(@&weatherInfo---&%@&,weatherInfo);
//取值打印
txtView.text = [NSString stringWithFormat:@&今天是 %@
的天气状况是:%@
%@ &,[weatherInfo objectForKey:@&date_y&],[weatherInfo objectForKey:@&week&],[weatherInfo objectForKey:@&city&], [weatherInfo objectForKey:@&weather1&], [weatherInfo objectForKey:@&temp1&]];
}(2)使用SBJson解析方法:(需导入包:#import &SBJson/SBJson.h&)
//使用SBJson解析南阳的天气
- (IBAction)btnPressSBJson:(id)sender {
NSURL *url = [NSURL URLWithString:@&.cn/data/.html&];
NSError *error =
NSString *jsonString = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&error];
SBJsonParser *parser = [[SBJsonParser alloc] init];
NSDictionary *rootDic = [parser objectWithString:jsonString error:&error];
NSDictionary *weatherInfo = [rootDic objectForKey:@&weatherinfo&];
txtView.text = [NSString stringWithFormat:@&今天是 %@
的天气状况是:%@
%@ &,[weatherInfo objectForKey:@&date_y&],[weatherInfo objectForKey:@&week&],[weatherInfo objectForKey:@&city&], [weatherInfo objectForKey:@&weather1&], [weatherInfo objectForKey:@&temp1&]];
}(3)使用IOS5自带解析类NSJSONSerialization方法解析:(无需导入包,IOS5支持,低版本IOS不支持)
- (IBAction)btnPressIOS5Json:(id)sender {
//加载一个NSURL对象
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@&.cn/data/.html&]];
//将请求的url数据放到NSData对象中
NSData *response = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
//IOS5自带解析类NSJSONSerialization从response中解析出数据放到字典中
NSDictionary *weatherDic = [NSJSONSerialization JSONObjectWithData:response options:NSJSONReadingMutableLeaves error:&error];
NSDictionary *weatherInfo = [weatherDic objectForKey:@&weatherinfo&];
txtView.text = [NSString stringWithFormat:@&今天是 %@
的天气状况是:%@
%@ &,[weatherInfo objectForKey:@&date_y&],[weatherInfo objectForKey:@&week&],[weatherInfo objectForKey:@&city&], [weatherInfo objectForKey:@&weather1&], [weatherInfo objectForKey:@&temp1&]];
NSLog(@&weatherInfo字典里面的内容为--》%@&, weatherDic );
}(4)使用JSONKit的解析方法:(需导入包:#import &JSONKit/JSONKit.h&)
- (IBAction)btnPressJsonKit:(id)sender {
//如果json是“单层”的,即value都是字符串、数字,可以使用objectFromJSONString
NSString *json1 = @&{\&a\&:123, \&b\&:\&abc\&}&;
NSLog(@&json1:%@&,json1);
NSDictionary *data1 = [json1 objectFromJSONString];
NSLog(@&json1.a:%@&,[data1 objectForKey:@&a&]);
NSLog(@&json1.b:%@&,[data1 objectForKey:@&b&]);
[json1 release];
//如果json有嵌套,即value里有array、object,如果再使用objectFromJSONString,程序可能会报错(测试结果表明:使用由网络或得到的php/json_encode生成的json时会报错,但使用NSString定义的json字符串时,解析成功),最好使用objectFromJSONStringWithParseOptions:
NSString *json2 = @&{\&a\&:123, \&b\&:\&abc\&, \&c\&:[456, \&hello\&], \&d\&:{\&name\&:\&张三\&, \&age\&:\&32\&}}&;
NSLog(@&json2:%@&, json2);
NSDictionary *data2 = [json2 objectFromJSONStringWithParseOptions:JKParseOptionLooseUnicode];
NSLog(@&json2.c:%@&, [data2 objectForKey:@&c&]);
NSLog(@&json2.d:%@&, [data2 objectForKey:@&d&]);
[json2 release];
另外,由于iOS5新增了JSON解析的API,我们将其和其他五个开源的JSON解析库进行了解析速度的测试,下面是测试的结果。
我们选择的测试对象包含下面的这几个框架,其中NSJSONSerialization是iOS5系统新增的JSON解析的API,需要iOS5的环境,如果您在更低的版本进行测试,应该屏蔽相应的代码调用。
- [SBJSON (json-framework)](/p/json-framework/)
- [TouchJSON (from touchcode)](/p/touchcode/)
- [YAJL (objective-C bindings)](/gabriel/yajl-objc)
- [JSONKit](/johnezang/JSONKit)
- [NextiveJson](/nextive/NextiveJson)&
-[NSJSONSerialization](/library/ios/#documentation/Foundation/Reference/NSJSONSerialization_Class/Reference/Reference.html#//apple_ref/doc/uid/TP)
&我们选择了四个包含json格式的数据的文件进行测试。每一个文件进行100的解析动作,对解析的时间进行比较。
测试的结果显示,系统的API的解析速度最快,我们在工程项目中选择使用,也是应用较为广泛的SBJSON的解析速度为倒数第二差,令我大跌眼镜。
与系统API较为接近的应该是JSONKit。
这里没有对API的开放接口和使用方式进行比较,若单纯基于以上解析速度的测试:
1:iOS5应该选择系统的API进行
2:不能使用系统API的应该选择JSONKit
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:648779次
积分:5326
积分:5326
排名:第2087名
原创:59篇
转载:22篇
评论:188条
(1)(2)(1)(1)(2)(4)(2)(4)(2)(4)(2)(1)(3)(13)(5)(2)(17)(15)随笔 - 60&
评论 - 5758&
&&&&&&&&&&&
我讲述了如何实现自己的服务框架,但我想很多人应该用过WebService这类服务框架,相比起来,似乎还缺少什么东西,
是的,我也感觉到了。比如:我可以很容易地利用WebService, WCF框架编写一个服务,
在客户端也可以很容易地通过【添加服务引用】的方式来生成一个代理类,然后就可以调用服务了,非常简单,
更酷的是,IDE生成的代理类还有异步调用功能!
我一直认为,对于服务框架来说,最重要的事是将一个C#方法公开为一个服务方法,供远程客户端调用。
因此,我上篇博客中演示的服务框架显然已经可以简单地完成这个功能。
不过,目前如果要使用这个服务框架,客户端还不够方便:
总不能让使用者自己写代码发送HTTP请求吧?嗯,基于我的服务框架的一些约定,实现这个包装不是问题,
但前面提到的IDE能生成异步调用的代理类,这个功能就必须实现了,否则我认为太不完美了。
我是个追求完美的人,而异步又是一个很重要的功能,我自然不能不实现它。今天我就继续上篇博客的内容,来谈谈客户端的各种异步实现方法。
说明:异步调用服务却与服务端无关,属于客户端的事情。
此处的客户端是相对服务端来说的,它可以是任何类型的应用程序。今天的主要话题是关于客户端的异步调用。
插个问题,为什么要实现异步,异步有什么好处?
答:简单来说,对于服务程序而言,异步处理可以提高吞吐量,
对于WinForm这类桌面客户端程序而言,将耗时任务采用异步实现可以改善用户体验,而且任务可以并行执行,提高响应速度。
示例项目介绍
今天我将演示如何在客户端中,以不同的异步方式调用一个服务方法。
为了让演示更有实战性,我已准备了一个完整的示例项目。如下图:
整个示例由四个小项目构成:
1. WebSite1 是一个用于发布服务的网站(也包含一些Asp.net异步的示例)。
2. MySimpleServiceClient是一个类库项目,包含了我封装的客户端类。
3. 服务的实现放在ServiceClassLibrary项目中。
4. WindowsFormsApplication1 是调用服务的客户端,这是一个WinForm项目。
   之所以要选WinForm做为客户端演示,是因为WinForm编程模型中对操作UI方面有更多的线程要求,
   如果有调用延迟也会特别明显,因此WinForm编程模型对异步的处理更为复杂。
   为了能让演示更有意义,我宁可选择WinForm程序做为服务的客户端,而不是不负责的选择Console程序。
   事实上,演示代码也适用于其它编程模型。
服务类的代码如下:
/// &summary&
/// 要做为服务发布的服务类,其实就是一个普通的C#类,加了一些Attribute而已。
/// 所有幕后的工作,全由服务框架来实现,关于服务框架请参考我的博客:
/// 【用Asp.net写自己的服务框架】
/// /fish-li/archive//2168073.html
/// &/summary&
[MyService]
public class DemoService
[MyServiceMethod]
public static string ExtractNumber(string str)
// 延迟3秒,模拟一个长时间的调用操作,便于客户演示异步的效果。
System.Threading.Thread.Sleep(3000);
if( string.IsNullOrEmpty(str) )
return "str IsNullOrEmpty.";
return new string((from c in str where Char.IsDigit(c) orderby c select c).ToArray());
服务方法的功能很简单:从一个字符串中找到所有数字,然后排序输出。
客户端运行界面如下:
同步调用服务
为了更好的理解异步调用,也为了和后面的异步调用做个比较,这里先示例如何采用同步的方式调用服务。代码如下:
/// &summary&
/// 同步调用服务,此时界面应该会【卡住】。
/// &/summary&
/// &param name="str"&&/param&
private void SyncCallService(string str)
string result = HttpWebRequestHelper.SendHttpRequest(ServiceUrl, str);
ShowResult(string.Format("{0} =& {1}", str, result));
catch( Exception ex ) {
ShowResult(string.Format("{0} =& Error: {1}", str, ex.Message));
其中,HttpWebRequestHelper.SendHttpRequest()最终调用的代码如下:
/// &summary&
/// 同步调用服务
/// &/summary&
/// &param name="url"&&/param&
/// &param name="input"&&/param&
/// &returns&&/returns&
public static TOut SendHttpRequest(string url, TIn input)
if( string.IsNullOrEmpty(url) )
throw new ArgumentNullException("url");
if( input == null )
throw new ArgumentNullException("input");
// 为了简单,这里仅使用JSON序列化方式
JavaScriptSerializer jss = new JavaScriptSerializer();
string jsonData = jss.Serialize(input);
// 创建请求对象
HttpWebRequest request = CreateHttpWebRequest(url, "json");
// 发送请求数据
using( BinaryWriter bw = new BinaryWriter(request.GetRequestStream()) ) {
bw.Write(DefaultEncoding.GetBytes(jsonData));
// 获取响应对象,并读取响应内容
using( HttpWebResponse response = (HttpWebResponse)request.GetResponse() ) {
string responseText = ReadResponse(response);
return jss.Deserialize&TOut&(responseText);
以上代码,也就是我前面所说的客户端的包装工具类了。有了它,就可以很容易地调用我的服务了。
代码中的CreateHttpWebRequest()以及ReadResponse()都很简单,而且与异步一点关系也没有,就不贴出了,可以在本文结尾处下载它们。
异步接口介绍
在开始介绍各种异步实现方法之前,有必要先明说一下:
在.net中,所有异步都是基于IAsyncResult这个最基础的接口。只是不同的API在具体实现时,创建的IAsyncResult实例不同,
以及封装方式不同而已。IAsyncResult的接口定义如下:
public interface IAsyncResult
// 获取用户定义的对象,它限定或包含关于异步操作的信息。
// 通常在调用BeginXXXX方法时传入对象,供回调方法时恢复之前的状态。
object AsyncState { get; }
// 获取用于等待异步操作完成的 System.Threading.WaitHandle。
// 我们可以调用它的WaitOne()方法等待调用完成。
WaitHandle AsyncWaitHandle { get; }
// 获取异步操作是否同步完成的指示。
// 如果异步操作同步完成,则为 true;否则为 false。
bool CompletedSynchronously { get; }
// 获取异步操作是否已完成的指示。
// 如果操作完成则为 true,否则为 false。
bool IsCompleted { get; }
下面我们再来看一下各种异步方法的实现方式。
1. 委托异步调用
对于任何一个方法,.net默认是采用同步的方式去调用,即:在调用时,后面的代码一直要等待调用完成后才能继续执行。
不过,我们可以使用委托,将一个方法按异步的方式去调用。对于前面的同步调用代码,我可以使用委托来完成异步的调用:
/// &summary&
/// 委托异步调用
/// &/summary&
/// &param name="str"&&/param&
private void CallViaDelegate(string str)
Func&string, string, string& func = HttpWebRequestHelper.SendHttpR
func.BeginInvoke(ServiceUrl, str, CallViaDelegateCallback, str);
private void CallViaDelegateCallback(IAsyncResult ar)
string str = (string)ar.AsyncS
Func&string, string, string& func
= (ar as AsyncResult).AsyncDelegate as Func&string, string, string&;
// 如果有异常,会在这里被重新抛出。
string result = func.EndInvoke(ar);
ShowResult(string.Format("{0} =& {1}", str, result));
catch( Exception ex ) {
ShowResult(string.Format("{0} =& Error: {1}", str, ex.Message));
说到BeginInvoke,EndInvoke就不得不停下来看一下委托的本质。为了便于理解委托,我定义一个简单的委托:
public delegate string MyFunc(int num, DateTime dt);
我们再来看一下这个委托在编译后的程序集中是个什么样的:
委托被编译成一个新的类型,拥有BeginInvoke,EndInvoke,Invoke这三个方法。前二个方法的组合使用便可实现异步调用。第三个方法将以同步的方式调用。
其中BeginInvoke方法的最后二个参数用于回调,其它参数则与委托的包装方法的输入参数是匹配的。
EndInvoke的返回值与委托的包装方法的返回值匹配。
注意:委托的BeginInvoke方法在调用后,也会返回一个IAsyncResult对象(类型为:System.Runtime.Remoting.Messaging.AsyncResult)。在IDE窗口中,我们也可以在智能提示中看到如下提示信息:
因此,我们也可以不使用回调方法,而是直接使用它的返回值,并在一个【恰当的时候】结束异步调用(其实是以同步的方式并行执行任务)。如下代码所示:
private void CallViaDelegate_X2(string str)
Func&string, string, string& func = HttpWebRequestHelper.SendHttpR
IAsyncResult ar = func.BeginInvoke(ServiceUrl, str, null, null);
// 在此执行其它的计算操作,
// 也可以在此再发起另一个异步调用。
string result = func.EndInvoke(ar);
//...处理结果
ShowResult(string.Format("{0} =& {1}", str, result));
小结:使用委托的异步调用方式很简单,只要用一个方法创建一个委托对象,然后调用BeginInvoke方法就可以了。
对BeginInvoke()方法的调用是以异步方式进行,但对于调用EndInvoke()方法则是以同步方式进行的(如果任务没有执行完,将会发生阻塞)。如果您想实现无阻塞的异步,
可以在调用BeginInvoke()方法时指定回调方法,那么在异步完成时,回调方法将被调用,此时对EndInvoke()的调用将不会阻塞线程。
异常的处理:在委托的异步实现中,由于BeginInvoke的调用是无阻塞的,此时方法将立即返回,而异常则是在任务执行过程中引发的,
因此,异常只能在调用EndInvoke时重新抛出,所以,也只能在调用EndInvoke时捕获异常。如果采用委托的方式异步调用某个没有返回值的方法,
那么,当你不调用EndInvoke时,你是不知道是否有异常抛出的。
注意:委托的异步调用是将任务交给线程池的工作线程来执行的。
证明这个说法很简单,可以在任务中加以如下代码,然后设置断点观察变量的取值即可:
bool isThreadPoolThread = System.Threading.Thread.CurrentThread.IsThreadPoolT
2. 使用IAsyncResult接口实现异步调用
在.net framework中,许多I/O操作(文件I/O操作以及网络I/O)都提供异步版本的API,我们可以直接使用这些API来达到异步调用的目的。
在今天的示例中,发送HTTP请求的API中,就支持异步操作,我将演示使用这些异步API的操作过程。
在客户端,我将使用以下代码完成异步调用过程:
/// &summary&
/// 使用IAsyncResult接口实现异步调用
/// &/summary&
/// &param name="str"&&/param&
private void CallViaIAsyncResult(string str)
HttpWebRequestHelper.SendHttpRequestAsync(ServiceUrl, str, CallViaIAsyncResultCallback, null);
private void CallViaIAsyncResultCallback(string str, string result, Exception ex, object state)
if( ex == null )
ShowResult(string.Format("{0} =& {1}", str, result));
ShowResult(string.Format("{0} =& Error: {1}", str, ex.Message));
其中HttpWebRequestHelper.SendHttpRequestAsync()是个简单的包装方法,最终异步操作的实现代码如下:
/// &summary&
/// 用于所有回调状态的数据类
/// &/summary&
private class MyCallbackParam
public TIn InputD
public Action&TIn, TOut, Exception, object& C
public object S
public HttpWebRequest R
public JavaScriptSerializer J
/// &summary&
/// 异步调用服务
/// &/summary&
/// &param name="url"&&/param&
/// &param name="input"&&/param&
/// &param name="callback"&服务调用完成后的回调委托,用于处理调用结果&/param&
/// &param name="state"&&/param&
public static void SendHttpRequestAsync(string url, TIn input,
Action&TIn, TOut, Exception, object& callback, object state)
if( string.IsNullOrEmpty(url) )
throw new ArgumentNullException("url");
if( input == null )
throw new ArgumentNullException("input");
if( callback == null )
throw new ArgumentNullException("callback");
// 创建请求对象
HttpWebRequest request = CreateHttpWebRequest(url, "json");
// 记录必要的回调参数
MyCallbackParam cp = new MyCallbackParam {
Callback = callback,
InputData = input,
State = state,
Request = request,
// 开始异步写入请求数据
request.BeginGetRequestStream(AsyncWriteRequestStream, cp);
// 虽然BeginGetRequestStream()可以返回一个IAsyncResult对象,
// 但我却不想返回这个对象,因为整个过程需要二次异步。
private static void AsyncWriteRequestStream(IAsyncResult ar)
// 取出回调前的状态参数
MyCallbackParam cp = (MyCallbackParam)ar.AsyncS
// 为了简单,这里仅使用JSON序列化方式
JavaScriptSerializer jss = new JavaScriptSerializer();
string jsonData = jss.Serialize(cp.InputData);
// 结束写入数据的操作
using( BinaryWriter bw = new BinaryWriter(cp.Request.EndGetRequestStream(ar)) ) {
bw.Write(DefaultEncoding.GetBytes(jsonData));
// 开始异步向服务器发起请求
cp.Request.BeginGetResponse(GetResponseCallback, cp);
catch( Exception ex ) {
cp.Callback(cp.InputData, default(TOut), ex, cp.State);
private static void GetResponseCallback(IAsyncResult ar)
// 取出回调前的状态参数
MyCallbackParam cp = (MyCallbackParam)ar.AsyncS
// 读取服务器的响应
using( HttpWebResponse response = (HttpWebResponse)cp.Request.EndGetResponse(ar) ) {
string responseText = ReadResponse(response);
TOut result = cp.Jss.Deserialize&TOut&(responseText);
// 返回结果,通过回调用户的回调方法来完成。
cp.Callback(cp.InputData, result, null, cp.State);
catch( Exception ex ) {
cp.Callback(cp.InputData, default(TOut), ex, cp.State);
注意:在SendHttpRequestAsync方法的实现过程中,需要发起二次异步调用:BeginGetRequestStream, BeginGetResponse 。自然地,
也会引起二次回调,二次EndXXXXX()方法的调用。为了能在回调过程中,维持一些必要的状态参数,我定义了一个私有类型MyCallbackParam ,
它包含了所有回调过程中所需要的中间状态。这里尤其要注意的是:如果某个异步操作过程需要多次异步调用,那么每个步骤都要求是异步的,
也就是要【一路异步到底】。如果中间任何一个步骤不是异步调用的,那么整个过程将不会是异步的,甚至某些API的设计者会抛出一个异常,这也是有可能的。
为了支持异步,我的包装方法也是通过回调的方式来设计的。这些都是异步设计的关键。
当某个异步操作过程需要多次调用时,该如何知道哪些步骤必须以异步形式调用呢?
比如,前面演示的发送HTTP请求的过程,我该如何知道要调用BeginGetRequestStream,BeginGetResponse这二个异步方法呢?
对于这个问题,没有一个明确的答案,因为在这方面,并没有一个规范或者约定,要根据相应组件的具体实现过程而定。
不过,通常每个支持异步组件所提供的API接口都会以BeginXxxxxx,EndXxxxxx的形式表示支持异步操作,并提供一个Xxxxxx的同步版本。
这里我可以提供一个小经验:逐个将一些关键步骤的同步调用替换成异步调用,直到实现异步过程为止。
再补充一句:对于微软提供的组件,查阅MSDN对于该方法的说明一般是可以找到线索的。
或许有些人不想定义这些回调方法,以及用于维护回调的状态类型,而选择闭包的方式。这种方法就技术的实现而言,也是可行的。
这里我就不演示了,因为我不喜欢搞大的闭包。
如果您喜欢闭包的方式,也请不要批我,每个人有每个人的喜好。
异常的处理:与委托的异步调用一样,此时也只能在调用EndXxxxxx时捕获异常。
不过,对于一个有着多个异步调用步骤的过程来说,异常的处理将要分阶段处理。
与委托异步调用的差别:
由于委托的BeginInvoke调用也能返回IAsyncResult,因此前面演示的委托的【同步并行执行】方式也可以在BeginXxxxxx/EndXxxxxx所支持的过程中使用。
但本小节所说的异步与委托的异步还是有差别,最重要的差别在于委托的异步调用阻塞发生在线程池的工作线程,
而直接使用基于IAsyncResult的异步,阻塞发生在线程池的I/O完成线程。这二种不同的线程对于不同的编程模型来说,意义是非常重大的。
小结:如果某个组件提供BeginXxxxxx/EndXxxxxx方法,通常表示可以支持异步操作,只要我们正确地调用它们就可以实现异步。
3. 基于事件的异步调用模式
前面我已演示了二种异步的使用方法,与同步调用相比,复杂性是很明显的。尤其对于WinForm这类编程模型时,
在回调时,肯定是不能操作UI界面的。因此,更是增加了使用上的难度。因此,有没有更方便地使用异步的方法?
我想这是每个开发人员想知道的。幸运的是,随着.net 2.0的发布,一种【基于事件的异步模式】的新模式出现了,
比如:IDE生成WebService的代理类就支持这种异步模式,此外,还增加了一个新的组件:BackgroundWorker,
它们完美地演示了如何方便地在各种编程模型中使用异步,并能在异步完成时,以我们熟知的事件方式处理后续操作。
比如,我可以使用以下代码就可以完成与前面一样的异步调用:
/// &summary&
/// 基于事件的异步模式
/// &/summary&
/// &param name="str"&&/param&
private void CallViaEvent(string str)
MyAysncClient&string, string& client = new MyAysncClient&string, string&(ServiceUrl);
client.OnCallCompleted += new MyAysncClient&string, string&.CallCompletedEventHandler(client_OnCallCompleted);
client.CallAysnc(str, str);
void client_OnCallCompleted(object sender, MyAysncClient&string, string&.CallCompletedEventArgs e)
//bool flag = txtOutput.InvokeR
// 注意:这里flag的值是false,也就是说可以直接操作UI界面
if( e.Error == null )
ShowResult(string.Format("{0} =& {1}", e.UserState, e.Result));
ShowResult(string.Format("{0} =& Error: {1}", e.UserState, e.Error.Message));
这里尤其要说明的是,虽然还是二个方法,但有了很大的差别:第二个方法可以当我在订阅OnCallCompleted事件时,
IDE可以帮我生成这个方法的空壳,我只要简单地显示结果就可以了,更为关键的是,此时的线程上下文已经和前面的异步方式不一样了,
而且调用参数也简单了。
对于组件的使用者而言,能支持这样的调用方式,的确是方便了。
为了让不同的编程模型不受线程问题困扰,以及支持事件通知,组件设计者应该提供这种接口模式。
不过,这个模式的背后实现要复杂一点,以下我将用代码来展示如何实现这种事件通知功能。
(注意代码中的注释,实现原理全在注释中)
/// &summary&
/// 我的异步调用客户端封装类
/// &/summary&
/// &typeparam name="TIn"&&/typeparam&
/// &typeparam name="TOut"&&/typeparam&
public sealed class MyAysncClient&TIn, TOut&
private string _
private volatile bool _isB
public bool IsBusy { get { return _isB } }
public MyAysncClient(string url)
if( string.IsNullOrEmpty(url) )
throw new ArgumentNullException("url");
/// &summary&
/// 调用完成后的事件参数类。它包含调用的结果,以及异常信息。
/// &/summary&
public class CallCompletedEventArgs : AsyncCompletedEventArgs
private TOut _
public CallCompletedEventArgs(TOut result, Exception e, bool canceled, object state)
: base(e, canceled, state)
public TOut Result
base.RaiseExceptionIfNecessary();
public delegate void CallCompletedEventHandler(object sender, CallCompletedEventArgs e);
/// &summary&
/// 异步调用完成后的通知事件
/// &/summary&
public event CallCompletedEventHandler OnCallC
public void CallAysnc(TIn input, object state)
if( input == null )
throw new ArgumentNullException("input");
if( _isBusy )
throw new InvalidOperationException("client is busy.");
// 准备与同步上下文有关的对象
// 注意这个调用,这是整个事件模式的核心。
AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(state);
//---------------------------------------------------------------------------------
这个客户端的封装类其实可以算是个辅助类,整个类就是辅助下面的这个调用。
这个类其实只处理二个简单的功能:
1. 引发异步调用完成后的事件。
2. 在合适的同步上下文环境中引发完成事件。
而真正发送请求的过程,在下面这个方法中实现的。
// 开始异步调用。这个方法将完成发送请求的过程。第三个参数为回调方法。
HttpWebRequestHelper&TIn, TOut&.SendHttpRequestAsync(_url, input, CallbackProc, asyncOp);
_isBusy = true;
// 异步完成的回调方法
private void CallbackProc(TIn input, TOut result, Exception exception, object state)
// 进入这个方法表示异步调用已完成。
AsyncOperation asyncOp = (AsyncOperation)
// 创建事件参数
CallCompletedEventArgs e =
new CallCompletedEventArgs(result, exception, false /* canceled */, asyncOp.UserSuppliedState);
// 切换线程调用上下文。注意第一个参数为回调方法。
asyncOp.PostOperationCompleted(CallCompleted, e);
// 用于处理完成后同步上下文切换的回调方法
private void CallCompleted(object args)
// 运行到这里表示已经切回当初发起调用CallAysnc()时的同步上下文环境。
CallCompletedEventArgs e = (CallCompletedEventArgs)
// 引发完成事件
CallCompletedEventHandler handler = OnCallC
if( handler != null )
handler(this, e);
// 到此,异步调用以及事件的响应全部处理结束。
_isBusy = false;
说明:此模式中,应使用AsyncOperationManager.CreateOperation()来创建一个异步操作对象,
异步通知事件的基类应该选择AsyncCompletedEventArgs,且对于派生类的属性仅要求实现get操作,并在返回前调用base.RaiseExceptionIfNecessary();
切换上下文的操作可调用asyncOp.PostOperationCompleted()来实现。
异常的处理:如果在异步执行过程中,引发了异常,此模式也要求引发完成事件,并在事件中告诉调用方具体的异常对象(基类有此属性)。
小结:IAsyncResult仍然是最根本的,事件模式也是建立在它之上,只是做了点包装而已。
但使用者却能够从中受益,因此,也是值得推荐的做法。
4. 创建新线程的异步方式
对于像WinForm这样的单线程编程模型来说,还可以通过创建新线程并将任务交给新线程来执行的方式达到异步效果。
这种方法很简单,只需要在新线程中调用同步任务,并在执行完成后通知界面就可以了。
以下代码演示了这种异步方式:
/// &summary&
/// 创建新线程的异步方式
/// &/summary&
/// &param name="str"&&/param&
private void CreateThread(string str)
Thread thread = new Thread(ThreadProc);
thread.IsBackground = true;
thread.Start(str);
private void ThreadProc(object obj)
string str = (string)
// 由于是在后台线程中,这里就直接调用同步方法。
SyncCallService(str);
catch( Exception ex ) {
ShowResult(string.Format("{0} =& Error: {1}", str, ex.Message));
小结:对于单线程编程模型的程序来说,创建新线程并调用原有的同步方法,也能实现异步调,进而提高用户体验。
5. 使用线程池的异步方式
前面我提到委托的异步调用可以实现异步效果,它其实是在使用线程沲的线程来调用原有的同步方法。
既然这样,我们也可以直接使用线程沲来达到同类效果,而且在实现方式上与前面说到的创建新线程的方法非常类似,
并且,我将继续使用上面示例中创建的ThreadProc方法。代码如下:
/// &summary&
/// 直接使用线程池的异步方式
/// &/summary&
/// &param name="str"&&/param&
private void UseThreadPool(string str)
ThreadPool.QueueUserWorkItem(ThreadProc, str);
小结:与创建新线程或者委托异步类似,我们也可以直接使用线程池来实现异步调用,
只需要调用ThreadPool.QueueUserWorkItem()即可。
6. 使用BackgroundWorker实现异步调用
前面我已提过BackgroundWorker这个组件,这是个没有界面元素的组件,可用于任何编程模型,
使用它可以方便地将一个耗时的任务交给线程池中的工作线程来执行,此组件提供一系列事件能让调用者方便地使用后台线程。
这个组件还可以支持进度报告,以及任务取消的功能(都需要自行实现)。
例如:取消操作只是一个通知,要求调用者在DoWork方法中自行实现,
一般是在一个循环中执行任务,每次执行循环时,先检查组件的CancellationPending属性,
如果为true,则表示已调用CancelAsync()方法请求取消任务。
下面我来简单地演示一下这个组件的使用:
/// &summary&
/// 使用BackgroundWorker实现异步调用
/// &/summary&
/// &param name="str"&&/param&
private void UseBackgroundWorker(string str)
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
worker.RunWorkerAsync(str);
void worker_DoWork(object sender, DoWorkEventArgs e)
//bool isThreadPoolThread = System.Threading.Thread.CurrentThread.IsThreadPoolT
string str = (string)e.A
string result = HttpWebRequestHelper.SendHttpRequest(ServiceUrl, str);
// 这个结果将在RunWorkerCompleted事件中使用
e.Result = string.Format("{0} =& {1}", str, result);
void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
//bool isThreadPoolThread = System.Threading.Thread.CurrentThread.IsThreadPoolT
// 此时可以直接使用UI控件。
if( e.Error != null )
txtOutput.Text += "\r\n" + string.Format("Error: {0}", e.Error.Message);
txtOutput.Text += "\r\n" + (string)e.R
请注意那些被我注释的代码,您可以取消注释并在调试状态下观察这些变量的值,以加深对这个组件使用线程的理解。
小结:BackgroundWorker可以非常方便地让我们使用后台线程,尤其适合WinForm这样的编程模型,
它解决了线程之间的沟通的复杂性。这里我将引用MSDN对于这个组件的描述:
BackgroundWorker 类允许您在单独的专用线程上运行操作。耗时的操作(如下载和数据库事务)在长时间运行时可能会导致用户界面 (UI) 似乎处于停止响应状态。如果您需要能进行响应的用户界面,而且面临与这类操作相关的长时间延迟,则可以使用 BackgroundWorker 类方便地解决问题。
客户端的其它代码
前面贴出了这个WinForm客户端的部分实现调用服务的代码。为了方便大家直接阅读,我将贴出另一些与这些调用有关的代码。
由于WinForm的特殊性:控件只能由UI线程操作,因此处理显示结果也需要特殊的处理:
/// &summary&
/// 显示结果
/// &/summary&
/// &param name="line"&&/param&
private void ShowResult(string line)
// 可以在这个方法中设置断点观察这些变量的状态(在使用前取消注释)。
// 注意要对比各种调用方式的差别。
//bool isBackground = System.Threading.Thread.CurrentThread.IsB
//bool isThreadPoolThread = System.Threading.Thread.CurrentThread.IsThreadPoolT
if( txtOutput.InvokeRequired )
// 采用同步上下文的方式切换线程调用。
_syncContext.Post(x =& txtOutput.Text += "\r\n" + line, null /*直接使用闭包参数*/);
txtOutput.Text += "\r\n" +
如果txtOutput.InvokeRequired为true,表示当前线程不是UI线程,此时不能直接修改控件内容。
此时,我为了简单,采用SynchronizationContext的方式来处理。相关的变量定义如下:
private SynchronizationContext _syncC
public Form1()
InitializeComponent();
_syncContext = SynchronizationContext.C
前面列出了每个调用服务的事件处理方法,这些方法是在这里被统一调用的:
private void btnCall_Click(object sender, EventArgs e)
string str = txtInput.Text.Trim();
if( str.Length == 0 ) {
MessageBox.Show("没有要处理的字符串。",
this.Text, MessageBoxButtons.OK, MessageBoxIcon.Information);
txtInput.Focus();
string method = (
from c in this.groupBox1.Controls.OfType&RadioButton&()
where c.Checked
select c.Tag.ToString()
).First();
this.GetType().InvokeMember(method,
BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.NonPublic,
null, this, new object[] { str });
说明:为了简单,避免一堆机械式的判断,我为每个RadioButton设置了Tag属性,指向要调用的方法名称。
各种异步方式的优缺点
前面我已介绍了6种不同的异步实现方法,但这些方法并不适合所有的编程模型。
因为异步只是将原来需要等待的时机转移到了其它线程,
不同的编程模型在线程的使用上又存在差别,因此,在具体的编程模型中,选择合适的异步方法也很重要。
本小节将来讨论这个话题,并对不同的异步方法做个简单的优缺点分析。
1. 委托异步调用:由于它的实现是将原本需要阻塞的操作交给线程池的工作线程来处理了,
此时线程池的工作线程被阻塞了。因此,此方法对于依赖【线程池的工作线程】来处理任务的编程模型来说是没有意义的。
比如:Asp.net, Windows Services这类服务类的编程模型。但对于WinForm这样的单线程编程模型来说,
是比较方便的,尤其是还可以实现并行执行一些任务。
2. 使用IAsyncResult接口实现异步调用:它的实现是将原本需要阻塞的操作交给线程池的I/O完成线程来处理了,
而在.net中,还没有任何编程模型使用此类线程来执行处理任务,因此,适合于任何编程模型。
但并不是所有的API都支持此类接口,因此适用面有限,且使用较为复杂,尤其是某个过程需要多次异步调用时。
一般说来,许多I/O操作(文件I/O操作以及网络I/O)是支持此类API接口的。
3. 基于事件的异步调用模式:这种方式可以认为是一种封装模式,主要是为了简化线程模型以及简化调用方式,增强了API的易用性。
如果此模式用于对IAsyncResult接口(并非委托异步)实现包装,那么它具有第2种方法的所有优点。
4. 创建新线程的异步方式:这种方式有点特殊,主要和什么样的编程模型以及创建了多少线程有关。
对于服务类的编程模型来说,如果每次的请求处理都采用这种方式,显然会创建大量线程,反而损害性能。
反之,在其它情况下也是可以考虑的。
5. 使用线程池的异步方式:基本上与第1种相似,不适合一些服务类的编程模型,仅仅适用于与用户交互的桌面程序。
6. 使用BackgroundWorker的方式:其实也是在使用线程池的工作线程,因此最适用的领域与1,5相似,只是它在使用上更方便而已。
一般说来,在.net中,标准的异步模式都是使用IAsyncResult接口,
因此后三种方法并不算是真正的异步,但它们却实可以在某些场合下实现异步效果。
我并没有找到一个关于异步的明确定义,因此希望这句话不会误导大家。
异步文件I/O操作
前面说到,在微软的实现中,一些常见的I/O操作API都支持返回IAsyncResult接口,它们是效率最好的异步方式。
下面我将继续演示文件I/O操作以及网络I/O(远程调用)这二类异步操作。
.net中支持文件异步操作的功能由FileStream类来实现的,FileStream类中与异步有关的成员定义如下:
公开以文件为主的 System.IO.Stream,既支持同步读写操作,也支持异步读写操作。
public class FileStream : Stream
// 使用指定的路径、创建模式、读/写和共享权限、
// 缓冲区大小和同步或异步状态初始化 System.IO.FileStream 类的新实例。
public FileStream(string path, FileMode mode, FileAccess access, FileShare share,
int bufferSize, bool useAsync);
// 获取一个值,该值指示 FileStream 是异步还是同步打开的。
// 如果 FileStream 是异步打开的,则为 true,否则为 false。
public virtual bool IsAsync { get; }
// 开始异步读。
public override IAsyncResult BeginRead(byte[] array, int offset, int numBytes,
AsyncCallback userCallback, object stateObject);
// 开始异步写。
public override IAsyncResult BeginWrite(byte[] array, int offset, int numBytes,
AsyncCallback userCallback, object stateObject);
// 等待挂起的异步读取完成。
public override int EndRead(IAsyncResult asyncResult);
// 结束异步写入,在 I/O 操作完成之前一直阻止。
public override void EndWrite(IAsyncResult asyncResult);
如果您需要使用文件的异步读写操作,请注意要使用上面列出的构造方法,并将最后一个参数设为true 。
关于这个参数,MSDN给出了详细的解释:
类型:System.Boolean
指定使用异步 I/O 还是同步 I/O。但是,请注意,基础操作系统可能不支持异步 I/O,因此在指定 true 后,根据所用平台,句柄可能同步打开。当异步打开时,BeginRead 和 BeginWrite 方法在执行大量读或写时效果更好,但对于少量的读/写,这些方法速度可能要慢得多。如果应用程序打算利用异步 I/O,将 useAsync 参数设置为 true。正确使用异步 I/O,可以使应用程序的速度加快 10 倍,但是如果在没有为异步 I/O 重新设计应用程序的情况下使用异步 I/O,则可能使性能降低 10 倍。
还有一点要特别提醒:
在 Windows 上,所有小于 64 KB 的 I/O 操作都将同步完成,以获得更高的性能。当缓冲区大小小于 64 KB 时,异步 I/O 可能会妨碍性能。
由于异步文件操作的使用并不常见,我也实在找不出一个有意见的场景演示这些操作,因此就不给出示例了。
MSDN中有一段这方面的示例代码:
数据库的异步操作
【网络I/O】其实是一个含糊的说法,它包含所有与网络调用有关的操作,
如:网络调用(WebService, Remoting, WCF),或者用底层的方式发送HTTP, TCP请求(WebRequest, FTP, Socket)。
对于数据库的操作,由于也需要经过网络调用,因此,访问数据库的API也可以支持异步操作(需要各自实现)。
在.net中,由于微软仅对SQL SERVER的访问实现了异步操作,因此,我也只能演示对SQL SERVER的异步调用。
以下代码演示了在WinForm中采用异步的方式获取数据库中由用户创建的数据库名称列表。
/// &summary&
/// 获取数据库中所有由用户创建的数据库的查询语句。注意我特意延迟了3秒。
/// &/summary&
private static readonly string s_QueryDatabaseListScript =
WAITFOR DELAY '00:00:03';
SELECT dtb.name AS [Database_Name] FROM master.sys.databases AS dtb
WHERE (CAST(case when dtb.name in ('master','model','msdb','tempdb') then 1 else dtb.is_distributor end AS bit)=0
and CAST(isnull(dtb.source_database_id, 0) AS bit)=0)
ORDER BY [Database_Name] ASC";
private void linkLabel3_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
Action action = BeginExecuteR
// 采用委托的异步调用防止在打开连接时界面停止响应。
// 这里并不需要回调。相当于 OneWay 的操作。
action.BeginInvoke(null, null);
private void BeginExecuteReader()
string connectionString = @"server=localhost\Integrated Security=SSPI;Asynchronous Processing=true";
SqlConnection connection = new SqlConnection(connectionString);
// 注意:这里是同步调用,第一次连接或者连接字符串无效时会让界面停止响应。
connection.Open();
catch( Exception ex ) {
ShowResult(ex.Message + "\r\n当前连接字符串:" + connectionString);
SqlCommand command = new SqlCommand(s_QueryDatabaseListScript, connection);
command.BeginExecuteReader(EndExecuteReader, command);
private void EndExecuteReader(IAsyncResult ar)
SqlCommand command = (SqlCommand)ar.AsyncS
StringBuilder sb = new StringBuilder();
// 如果SQL语句有错误,会在这里抛出。
using( SqlDataReader reader = command.EndExecuteReader(ar) ) {
while( reader.Read() ) {
sb.Append(reader.GetString(0)).Append("; ");
catch( Exception ex ) {
ShowResult(ex.Message);
command.Connection.Close();
if( sb.Length & 0 )
ShowResult("可用数据库列表:" + sb.ToString(0, sb.Length - 2));
代码很简单,我相信您能看懂,该讲的异步细节前面已说了,因此这里就不多说了,只有一点需要注意的是:
在连接字符串中必须要加入 Asynchronous Processing=true 。
当然了,您要是忘记了也没有关系,会有一个异常提示您的:
恰到好处的异常真是给力!
异步设计的使用总结
前面谈了许多关于异步实现的方法,涉及到一些.net中的API,也演示了一些我提供的示例代码。
下面再来总结一下在异步设计时需要遵循的一些惯用法。
由于目前的.net版本(4.0以内),语言本身、编译器、框架都没有很好的办法简化异步的实现或者规范设计要求,
因此,遵循一些惯用法将会使代码更容易让别人理解与使用,以可以在无形中避开一些怪异的错误。
以下是我总结的关于异步设计的一些惯用法:
1. 基础的异步操作通常会提供BeginXXXXX/EndXXXXX的一对方法用于完成某个异步操作,
这些方法通常会使用一个类型为IAsyncResult的对象。
通常,所有规范的异步API方法中,BeginXXXXX的最后二个参数应该是固定的:
倒数第二个参数是回调方法,最后一个参数则为回调方法所需要的必要状态数据。
如果状态数据要包含多个信息时,可以采用定义一个额外的类型来解决,这也是异步API方法的最后一个参数的类型是object的原因。
注意:对于BeginXXXXX方法的最后二个参数,都是允许为null的。
而EndXXXXX的方法的签名几乎是类似的:只有一个IAsyncResult类型的传入参数,返回值也是整个异步操作的结果,
如果在异步操作过程中发生异常时,也是在这个方法中重新抛出的。
因此,如果您要提供类似BeginXXXXX/EndXXXXX这种API,也请遵循这个惯用法。
2. 如果要采用异步事件模式包装您的API,请注意事件对象的基类应该选择AsyncCompletedEventArgs,并且应该将结果设计成只读属性,
并在返回前调用base.RaiseExceptionIfNecessary();以防止在调用失败时用户得到一个无效的结果。
开始异步调用的方法名也应该采用如下方式:MethodNameAsync 表示将启动一个异步过程。
3. 如果您提供了一个MethodNameAsync的异步方法,请考虑在方法的传入参数后面加一个【object state】的参数,
以便于向回调或者事件通知时,传入所需的状态数据。
说完了异步的惯用法,再来说说异步使用的注意事项:
1. 要实现无阻塞的异步调用过程,那么就要保证整个调用过程中,所有的操作都是异步的,也说是前面所说的【一路异步到底】。
通常我们可以采用传入回调函数的方式来实现无阻塞的调用过程。
2. 如果要实现自己的异步包装,请注意:需要在异步执行过程中捕获任何异常,并在执行完成后,用户试图获取结果时重新抛出。
3. 对于服务类的编程模型来说,异步仅能提高并发的访问量,如果此时服务器的压力已经足够大,那么使用异步是没有任何意义的。
4. 为了能维护一些异步回调时所需的必要数据,我不建议在客户端的调用类中,采用Field,Property的方式定义数据成员。
因为这样做的维护成本很高,尤其是在多次异步时,事实上,规范的异步方法的最后一个参数就是用于解决这个问题的!
在Asp.net中使用异步
由于Asp.net程序也可以调用我的服务框架,因此,这类程序相对于我的服务框架而言,也是客户端。
不过,Asp.net对于异步的实现方式有着特殊的要求,如果细说下去,将会造成这篇博客特别长,因此,我计划以后再谈这方面的话题。
为了不吊大家的胃口,我已准备好了一些关于Asp.net异步的示例代码,您要是有兴趣,可以先自行阅读,
以后有时间,我们再来聊这块内容。还有一点要提示:Asp.net的异步也是基于在前面所讲述的异步内容!
一些关于Asp.net的示例代码可以参考以下文件(红色方框内):
其中有个文件名是【JsCall.aspx】的页面,它则演示如何在浏览器中使用JavaScript调用我的服务框架或者调用ashx 。
它的操作界面如下:
至此,各种客户端的演示应该很全面了,我也可以安心结束这篇博客了。
阅读(...) 评论()}

我要回帖

更多关于 json lib 注释 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信