[转载]Android 为你的应用程序添加快捷方式

mikel阅读(1092)

[转载]Android 为你的应用程序添加快捷方式【优先级高的快捷方式】 – Terry_龙 – 博客园.

有人会说,快捷方式,不是安装完应用程序后,长按应用程序的ICON然后将它拖到桌面上不就行了吗?没错,这样是一种方法,但这种方法有一个缺点,看图吧:

如上图,如果我们长按桌面点击快捷方式,将会跳到如下界面,如果单从这个界面选择的话,我们就必须进入Applications 目录,然后再在Applications 里面选择我们对应的应用程序,这样的话用户可能得麻烦的去找咯。但我们同时会发现,在Applications 的下面有很多另外的ICON比如 上图的BookMark ,Contact 等,这些也是应用,那么这些是怎么做到不用进去Applications 而在第一页就出现供用户选择呢?今天我们就针对这点来讲讲吧。

要做这一功能首先我们先来了解一下manifest 里面的这一标签:

<activity-alias>

syntax:语法:
<activity-alias android:enabled=["true" | "false"]
                android:exported=["true" | "false"]
                android:icon="drawable resource"
                android:label="string resource"
                android:name="string"
                android:permission="string"
                android:targetActivity="string" >
    . . .
</activity-alias>
contained in:隶属于:
<application>
can contain:可以包含:
<intent-filter>
<meta-data>
description:说明:
An alias for an activity, named by the targetActivity attribute. The target must be in the same application as the alias and it must be declared before the alias in the manifest.
activity的一个别名,用targetActivity属性命名。目标activity必须与别名在同一应用程序的manifest里,并且在别名之前声明。
The alias presents the target activity as a independent entity. It can have its own set of intent filters, and they, rather than the intent filters on the target activity itself, determine which intents can activate the target through the alias and how the system treats the alias. For example, the intent filters on the alias may specify the “Android.intent.action.MAIN” and “Android.intent.category.LAUNCHER” flags, causing it to be represented in the application launcher, even though none of the filters on the target activity itself set these flags.
别名作为一个独立的实体代表目标activity。它可以有它自己的一套intent filter,它们,而不是目标activity自己的intent filter,决定哪个intent能够活性化目标通过别名以及系统如何处理别名。例如,别名的intent filter可以指定”android.intent.action.MAIN“和”android.intent.category.LAUNCHER“标签,使之显示在应用程序启动器上,即使目标activity自己没有设置这些标签。

With the exception of targetActivity, <activity-alias> attributes are a subset of <activity> attributes. For attributes in the subset, none of the values set for the target carry over to the alias. However, for attributes not in the subset, the values set for the target activity also apply to the alias.
targetActivity的例外,<activity-alias>属性是<activity>属性的一个子集。对于该子集中的属性,目标activity中设置的值不会覆盖别名的值。然而,对于那些子集中没有设置的属性,设置给目标activity的值同样适用于别名。

上面给出的解释我们来配置一下manifest,配置为如下:

<activity android:name=”.shortcut”>
<intent-filter>
<action android:name=”android.intent.action.MAIN” />
</intent-filter>
</activity>

<activity-alias android:name=”.CreateShortcuts”
android:targetActivity
=”.shortcut” android:label=”@string/shortcut”>

<intent-filter>
<action android:name=”android.intent.action.CREATE_SHORTCUT” />
<category android:name=”android.intent.category.DEFAULT” />
</intent-filter>

</activity-alias>

Activity:

.shortcut 是我们快捷方式需要的Activity

activity-alias:

对应的targetActivity是指向应用创建快捷方式使用的Activity

android:label对应的创建快捷方式列表显示的文字,而该应用对应的快捷方式的图标则默认使用我们给定的application的图标。如图:

好了,这是第一步步骤,下面进入代码阶段,先看代码:

package com.terry.attrs;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Parcelable;
import android.widget.LinearLayout;
import android.widget.TextView;

public class shortcut extends Activity {
private static final String SHORT_CUT_EXTRAS = com.terry.extra.short;

@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
final Intent intent
= getIntent();
final String action
= intent.getAction();
if (Intent.ACTION_CREATE_SHORTCUT.equals(action)) {
createShortCut();
finish();
return;
}

String extra = intent.getStringExtra(SHORT_CUT_EXTRAS);
LinearLayout layout
= new LinearLayout(getApplicationContext());
TextView tv
= new TextView(getApplicationContext());

if (extra != null)
tv.setText(extra);
layout.addView(tv);
setContentView(layout);
}

void createShortCut() {
Intent shortcutIntent
= new Intent(Intent.ACTION_MAIN);
shortcutIntent.setClass(
this, this.getClass());
shortcutIntent.putExtra(SHORT_CUT_EXTRAS,
测试的快捷方式);

Intent intent = new Intent();
intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
intent.putExtra(Intent.EXTRA_SHORTCUT_NAME,
这里随便指定);
Parcelable shortIcon
= Intent.ShortcutIconResource.fromContext(
this, com.terry.attrs.R.drawable.icon);
intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, shortIcon);
setResult(RESULT_OK, intent);
}
}

代码解释:

onCreate方法,首先获取intent 的action如果接收到的action为创建快捷方式的请求,则执行创建快捷方式的代码,否则则通过得到的extra 为textView 赋值。

createShortCut方法,首先设置快捷方式点击后要跳转的intent 和要带入的参数,然后设置桌面快捷方式的名称,图标和对应的intent(即上面带入数据和跳转的界面的 class的Intent)最后将结果传入。

最近运行的结果:

跳击后到达的界面:

TIP:这里可以是任何ACTIVITY界面。

最后给大家分享下源码吧:

快捷方式

就到这里,希望我的一篇废话能对你有所帮助。

[转载]条形码(barcode)系列(1)

mikel阅读(1031)

[转载]条形码(barcode)系列(1) – 专注实用,代码真实可用 – 博客园.

条形码(barcode)是将宽度不等的多个黑条和空白,按照一定的编码规则排列,用以表达一组信息的图形标识符。常见的条形码是由反射率相 差很大的黑条(简称条)和白条(简称空)排成的平行线图案。条形码可以标出物品的生产国、制造厂家、商品名称、生产日期、图书分类号、邮件起止地点、类 别、日期等许多信息,因而在商品流通、图书管理、邮政管理、银行系统等许多领域都得到了广泛的应用。

条形码的识别原理

要将按照一定规则编译出来的条形码转换成有意义的信息,需要经历扫描和译码两个过程。物体的颜色是由其反射光的类型决定的,白色物体能反射各种 波长的可见光,黑色物体则吸收各种波长的可见光,所以当条形码扫描器光源发出的光在条形码上反射后,反射光照射到条码扫描器内部的光电转换器上,光电转换 器根据强弱不同的反射光信号,转换成相应的电信号。根据原理的差异,扫描器可以分为光笔、CCD、激光三种。电信号输出到条码扫描器的放大电路增强信号之 后,再送到整形电路将模拟信号转换成数字信号。白条、黑条的宽度不同,相应的电信号持续时间长短也不同。 然后译码器通过测量脉冲数字电信号0,1的数目来判别条和空的数目。通过测量0,1信号持续的时间来判别条和空的宽度。此时所得到的数据仍然是杂乱无章 的,要知道条形码所包含的信息,则需根据对应的编码规则(例如:EAN-8码),将条形符号换成相应的数字、字符信息。最后,由计算机系统进行数据处理与 管理,物品的详细信息便被识别了。

条形码的优越性

可靠性强、效率高、成本低、易于制作、构造简单、灵活实用

条形码的扫描

条形码的扫描需要扫描器,扫描器利用自身光源照射条形码,再利用光电转换器接受反射的光线,将反射光线的明暗转换成数字信号。不论是采取何种规则印制的条形码,都由静区、起始字符、数据字符与终止字符组成。有些条码在数据字符与终止字符之间还有校验字符。

▲静区:静区也叫空白区,分为左空白区和右空白区,左空白区是让扫描设备做好扫描准备,右空白区是保证扫描设备正确识别条码的结束标记。   为了防止左右空白区(静区)在印刷排版时被无意中占用,可在空白区加印一个符号(左侧没有数字时印<号,右侧没有数字时加印>号)这个符 号就叫静区标记。主要作用就是防止静区宽度不足。只要静区宽度能保证,有没有这个符号都不影响条码的识别。

▲起始字符:第一位字符,具有特殊结构,当扫描器读取到该字符时,便开始正式读取代码了。

▲数据字符:条形码的主要内容。

▲校验字符:检验读取到的数据是否正确。不同编码规则可能会有不同的校验规则。

▲终止字符:最后一位字符,一样具有特殊结构,用于告知代码扫描完毕,同时还起到只是进行校验计算的作用。   为了方便双向扫描,起止字符具有不对称结构。因此扫描器扫描时可以自动对条码信息重新排列。 条码扫描器有光笔、CCD、激光三种

▲光笔:最原始的扫描方式,需要手动移动光笔,并且还要与条形码接触。

▲CCD:以CCD作为光电转换器,LED作为发光光源的扫描器。在一定范围内,可以实现自动扫描。并且可以阅读各种材料、不平表面上的条码,成本也较为低廉。但是与激光式相比,扫描距离较短。

▲激光:以激光作为发光源的扫描器。又可分为线型、全角度等几种。   线型:多用于手持式扫描器,范围远,准确性高。   全角度:多为卧式,自动化程度高,在各种方向上都可以自动读取条码。

常用条码类型

1) UPC码

1973年,美国率先在国内的商业系统中应用于UPC码之后加拿大也在商业系统中采用UPC码。UPC码是一种长度固定的连续型数字式码制,其 字符集为数字0~9。它采用四种元素宽度,每个条或空是1、2、3或4倍单位元素宽度。IPC码有两种类型,即UPC-A码和UPC-E码。

2) EAN码

1977年,欧洲经济共同体各国按照UPC码的标准制定了欧洲物品编码EAN码,与UPC码兼容,而且两者具有相同的符号体系。EAN码的字符 编号结构与UPC码相同,也是长度固定的、连续型的数字式码制,其字符集是数字0~9。它采用四种元素宽度,每个条或空是1、2、3或4倍单位元素宽度。 EAN码有两种类型,即EAN-13码和EAN-8码。

3)交叉25码

交叉25码是一种长度可变的连续型自校验数字式码制,其字符集为数字0~9。采用两种元素宽度,每个条和空是宽或窄元素。编码字符个数为偶数, 所有奇数位置上的数据以条编码,偶数位置上的数据以空编码。如果为奇数个数据编码,则在数据前补一位0,以使数据为偶数个数位。

4)39码

39码是第一个字母数字式码制。1974年由Intermec公司推出。它是长度可比的离散型自校险字母数字式码制。其字符集为数字0— 9,26个大写字母和7特殊字符(-、。、Space、/、%、¥),共43个字符。每个字符由9个元素组成,其中有5个条(2个宽条,3个窄条)和4个 空(1个宽空,3个窄空),是一种离散码。

5)库德巴码

库德巴码(Code Bar)出现于1972年,是一种长度可变的连续型自校验数字式码制。其字符集为数字0—9和6个特殊字符(-、:、/、。、+、¥),共16个字符。常用于仓库、血库和航空快递包裹中。

6)128码

128码出现于1981年,是一种长度可变的连续型自校验数字式码制。它采用四种元素宽度,每个字符由3个条和3个空,共11个单元元素宽度, 又称(11,3)码。它由106个不,同条形码字符,每个条形码字符有三种含义不同的字符集,分别为A、B、C。它使用这3个交替的字符集可将128个 ASCII码编码。

7)93码

93码是一种长度可变的连续型字母数字式码制。其字符集成为数字。0-9,26个大写字母和7个特殊字符(-、。、Space、/、+、%、¥)以及4个控制字符。每个字符由3个条和3个罕,共9个元素宽度。

8)49码

49码是一种多行的连续型、长度可变的字母数字式码制。出现于1987年,主要用于小物品标签上的符号。采用多种元素宽度。其字符集为数字 0-9,26个大写字母和7个特殊字符(-、。、Space、%、/、+、%、¥)、3个功能键(F1、 陀、F3)和3个变换字符,共49个字符。

9)其他码制

除上述码外,还有其他的码制,例如25码出现于1977年,主要用于电子元器件标签;矩阵25码是11码的变形;Nixdorf码已被EAN码所取代Plessey码出现于1971年5月主要用于图书馆等。

2、按维数分类

1) 普通的一维条码

普通的一维条码自本问世以来,很快得到了普及并广泛应用。但是由于一维条码的信息容量很小,如商品上的条码仅能容13位的阿拉伯数字,更多的描 述商品的信息只能依赖数据库的支持,离开了预先建立的数据库,这种条码就变成了无源之水,无本之木,因而条码的应用范围受到了一定的限制。

2) 二维条码

除具有普通条码的优点外,二维条码还具有信息容量大、可靠性高、保密防伪性强、易于制作、成本低等优点。<BR>美国Symbol 公司于1991年正式推出名为PDF417的二维条码,简称为PDF417条码,即“便携式数据文件”。FDF417条码是一种高密度、高信息含量的便携 式数据文件,是实现证件及卡片等大容量、高可靠性信息自动存储、携带并可用机器自动识读的理想手段。

3) 多维条码

进入20世纪80年代以来,人们围绕如何提高条形码符号的信息密度,进行了研究工作。多维条形码和集装箱条形码成为研究、以展与应用的方 向。<BR>信息密度是描述条形码符号的一个重要参数据,即单位长度中可能编写的字母个数,通常记作:字母个数/cm。影响信息密度的主要因 素是条、空结构和窄元系的宽度。<BR>128码和93码就是人们为提高密度而进行的成功的尝试。128码城1981年被推荐应用;而93码 于1982年投入使用。这两种码的符号密度均比39码高将近30%。<BR>随着条形码技术的发展和条形码三制的种类不断增加,条形码的标准 化显得愈来愈重要。为此,曾先后制定了军用标准1189;交叉25码、39码和Coda Bar码ANSI标准MH10.8M等。同时,一些行业也开始建立行业标准,以适应发展的需要。此后,戴维·阿利尔又研制出49码。这是一种非传统的条形 码符号,它比以往的条形码符号具有更高的密度。特德·威廉姆斯(Ted Williams)GFI988推出16K码,该码的结构类似于49码,是一种比较新型的码制,适用于激光系统。

以上内容来源于网络,整理而成。就写到这里吧,下一节以128码为例 语言C#

[转载]使用linq的ToArray() ToList() ToDictionary()

mikel阅读(1798)

[转载]Linq 中的琐碎东西 – 天涯走狗 – 博客园.

使用linq的ToArray() ToList() ToDictionary()

当我们对某个Ienumerable<T>对象下达where等条件式时,所取得的结果会是一个实现了 Ienumerable<T>接口的对象,此时所有指定的条件式都尚未执行比对的操作,而只是的到了一个WhereIterator对象,拿 到了由编译器对linq expression 进行分析之后所建立的delegate 和string。当通过此Ienumerable<T>对象获得Enumerator对象,并调用MoveNext函数时(foreach会 触发该函数),where Iterator开始起作用,该对象才会一笔一笔地对元素进行条件比对。这一模式不仅用于LINQ TO Objects, 同时也用于LINQ To XML, LINQ ToDataSet 乃至LINQ To SQL, LINQ TO Entities。

但ToArray()等函数会以foreach一一巡览Ieunmerable<T>对象中的所有元素并进行比较,然后将符合条件的元素方放在Array中返回。

那么,操作这样一个比较过的Array 或者 List 对象,显然比直接操作必须在取得元素前进行比对条件的对象集来的有效率

LINQ Expression无法完全取代函数调用。以Where为例。当使用函数时,我们可以写下很复杂的Lambda Exression

Var pq = new[]{"ybwang", "dingmeng", "yhzhou"}
Var p5 = p1.Where(
	x=>{
		Bool result = false;
		sqlConnection conn = new SqlConnection("…...");
		Conn.Open();
		Return result;
	}
)

这是无法以单纯的LINQ Expression 办到的,如果硬要以LINQ Expression 来完成,就必须使用静态函数或者是Extension Method 才行。所以学会如何调用Extension Method 以及运用 Lambda Expression, 是活用Linq to Object 的不二法门。

C#3.0只是单纯地把LINQ Expression 转成object.Select(),

如果你自己打造了一个类Persons<T>,

Public class Persons<T>{
	Private List<T> _list = new List<t>();
	Public T this[int index]
	{
		Get{
			Return _list[index];
		}
	}

	Public void add[T item]{
		_list.Add(item);
	}
}

你用from o in Persons1 select o 的时候,由于这个类没有实现Ienumerable<T>接口,则它将该Lambda表达式转换为Select()方法之后,会因为找不到这 个方法而报错。于是你得自己写个Extension方法,示例如下

public static class PersonsExtension{
	public static Persons<TResult> Select<TSource, TResult>(this Persons<TSource>, Func<TSource, TResult> selector){
		//select elements using the Fucn
		//return result;        
	}
}

可见,选择过程的控制权在我们手上

Linq to SQL 查询返回的那个东西,代表这一组SQL语句,也就是说你写下var result = from …..的时候并没有去数据库进行查询,当你真正用到数据时,比如对result进行foreach时,才会用result里头的sql语句去数据库进行查 询,查询过程 默认使用的是连接模式,即用sqlDataReader来读数据。

示例一

aDataContext c = new aDataContext();

var result1 = from c1 in c.Customers select c1;
var result2 = from c2 in c.Customers select c2;

foreach(var item in result1){
	foreach(var item2 in result2){
		//do something
	}
}

这是可以的,由于result1和result2属于同一个context,当result2开始去尝试连接数据库时,result1会把它要的数据全部提取出来,缓存到一个DataTable中,然后绑定到该DataTable,好让result2绑定到数据库

实例二

aDataContext c1 = new aDataContext();
aDataContext c2 = new aDataContext();

var result1 = from c1 in c1.Customers select c1;
var result2 = from c2 in c2.Customers select c2;

foreach(var item in result1){
	foreach(var item2 in result2){
		//do something
	}
}

就不行了,因为这种缓存机制之适应于同一个dataContext的不同result

有些适用于Linq to Object 的语法,并不适用于Linq to SQL

示例

static void main(string [] args){
	var list = new [] {"A", "B", "C"};
	var result = from s1 in list where CheckMe(s1) select s1;
	
	foreach(var item in result){}
}	

static bool CheckMe(string item){
	return true;
}


这是可以的,但在Linq to SQL中

static void main(string [] args){
	aDataContext context = new aDataContext();	
	var result = from s1 in context.Customers where CheckMe(s1) select s1;

	foreach(var item in result){}	
}

static bool CheckMe(string item){
	return true;
}

这是不行的,因为linq不知道该如何将CheckMe函数转化成sql语句。

这种情况,你可已先调用ToArray或者ToList函数,使得取回的对象变为一般的Collection,于是就进入了Linq to Object 的范围,可以用Linq to Object 来操作它了。

在LINQ TO SQL 默认模式中,调用submitChanges函数更新数据之前会激活一个Transaction,若期间发生任何错误时会抛出一个异常,终止更新操作并调用Transcation的RollBack函数来恢复事务前的状态。

Linq to sql 为Entity Class的每个属性提供了Delay Load 属性,当该属性的Delay Load被设置为True时,Linq to SQL于撷取时便不会撷取该字段,而是等到该属性第一次被访问时才下达一个SQL指令由数据库撷取该字段数据。往往将一些二进制data的 Delay Load设置成true

在lts(Linq To SQL)中使用select new 时,所select 出来的对象是只读的。这些对象和数据库里边的行并没有什么联系,不像是直接选出来的那些Entity Object, 你修改他们再调用context.Submit(),就会将修改提交到数据库

class Program{
	static void Main(string[] args){
		test();
	}

	static test(){
		aDataContext context = new aDataConetxt();
		var result = context.ExecuteQuery<OrdersWithTotal>("select orderId, sum(UnitPrice * Quantity) as Totla from [Order Details] group by orderId");

		foreach(var item in result){
			Console.WriteLine("Order Id: {0} Total : {1}", item.OrderId, item.Total);
		}
	}
}

public class OrdersWithTotal{
	public int OrderId{get; set}
	public Decimal Total{get; set}
}

当 Linq to Sql 无法满足要求时,可以用ExecuteQuery函数来直接执行SQL指令。它需要一个类型参数,该函数为每一行创建指定类型的对象,此处就是 OrderWithTotal.然后将相同名字的字段值填入同名的属性。这也是为何查询语句里会出现as Total 的缘故。as Total ,则生成的OrdersWithTotal对象的Total属性会是空的。

DataContext有一个Translate<T>(IDataReader)函数,用于从实现了IDataReader的对象中获取实体对象

相比于ExecuteQuery(), Translate()是架构与IDataReader之上,所以我们可以通过ODBC、OLEDB、Oracle等ADO.Net Provider将取得的IDataReader对象转成Entity Objects,对于转文件、汇入等功能来说,Translate函数相当好用。

但它有个限制,就是只能对返回对象做一次foreach,不过我们仍然可以通过ToList来跨越此限制

另外,该函数执行之后,所取得的Entity Object 会被加入到DataContext中,受dataContext所管辖,因此你可以对这些对象做修改,然后调用dataContext.Submit将改后的数据写回数据库

[转载]用Fiddler给别人的网站“优化”

mikel阅读(996)

[转载]用Fiddler给别人的网站“优化” – 梦想MVP – 博客园.

最近访问某知名网站的速度非常慢,有时候需要2分钟还没完全打开,页面展示了一半就卡住,然后等半天才继续显示 下面部分。这种情况已经有几个月了,不知道是他们服务器原因还是我所在网络的问题,但是基本上在其他网络访问速度也慢,如果是网站自身的问题,我自然没办 法解决,只能自己动手,先看看问题出在哪里,然后分析一下有没有可能绕过相关问题。于是用上了一个很好的HTTP分析工具,Fiddler Web Debugger

Fiddler的功能很强大,它不仅可以查看HTTP的通信信息,也可以进行分析,从而发现通信过程中的问题,为优化网站页面和提高性能提供依据。 另外,它提供了对url请求进行重定向处理(AutoResponder)的功能,用户可以在请求url时进行自定义处理,这也就为以制定方式访问页面提 供了可能性,这也是本文依靠的主要功能。

这个软件的具体使用方法可以参考《HTTP调试工具:Fiddler的使用方法介绍》本文只介绍如何用它来解决实际问题。

步骤一:获取与网站页面的通信信息

为了保证只监控到指定网页的通信信息,我只打开了一个IE,并且在fiddler设置只监控web browers(默认是All Processs)另外通过fiddler清除了IE缓存,从而能获取更加真实页面加载时间。然后在IE中输入相应网址(就不具体指明了)进行访问,这时 可以看到fiddler左侧出现许多访问这一网站的请求url,等待中……。直到IE状态栏显示加载完毕的提示“完成”,这时停止fiddler的通信捕 获,这样就完成了通信捕获。以下是局部抓图:

1

步骤二:分析性能瓶颈

接下来就是分析上面各个sessions(请求),找出哪些请求导致了加载页面慢,我一般按照如下步骤:

(1)找到不正确的请求

全选所有sessions,然后,选择右面的Statistics功能,这样就可以统计出这些sessions的总体信息,如下图(部分):

2

这里我关注的是从请求开始到返回请求的整个时间,这里消耗了2分56秒(Sequence (clock) time),显然,这是不可以接受的速度。然后我发现,在response codes中,统计的HTTP/502有12个,要知道,Http 5xx代表的是服务器错误,一般来说,这种错误会导致服务器反应很长时间才返回给客户端一个错误的代码信息,从而可能导致请求速度的急剧下降。有可能这个网站就是因为这些错误请求导致加载页面慢的。查了一下:

502 – 网关错误:如果试图运行的 CGI 脚本不返回有效的 HTTP 标头集,将出现此错误消息。若要解决此问题,必须调试 CGI 应用程序,以确定它为什么会将无效的 HTTP 信息传递给 IIS。

也就是说有12个url请求出现了这一错误。其实在左侧sessions中,也可以看出来,它以红色惊叹号开头,并且可以知道请求的url等信息。

3

(2)通过timeline功能查看所有消耗的时间

全选sessions后,在界面右侧的timeline功能,可以以图形方式统计请求各个文件耗费的时间(这里会以请求的文件为单位,将那些请求文件相同但是参数不同的url合并统计):

4

其中横坐标是时间,纵坐标是请求文件,可以分析一下究竟是页面还是图片等加载时间比较长,通过分析,确实是那12个错误请求导致的加载慢,其他的css、js和图片,消耗的时间可以忽略。

步骤三:解决性能瓶颈

性能问题本该是网站来解决的,我没那个能力去真正解决性能问题,这里只是通过别的方法,来绕过导致瓶颈的问题罢了。分析了一下这12个错误请求 url,归根结底就是一个adjs.php和gmjs.php,当然每个请求的参数有所不同。这其实在加载页面时候通过他们这些sessions前面图标 就可以看出是有问题的:

截图00

下箭头表示正在下载,然而,我观察了,在这个请求时,下载时间很长,卡住了很久,然后变成红色叹号图标,才继续请求别的url。

分析adjs.php和gmjs.php文件,从命名来看,应该是与页面中广告有关的,对于用户来说,不要这个请求也应该不影响浏览网页吧,所以这里就是要想办法在请求时候,如果是关于这两个文件的请求,想法子绕过去就好了。

这就用上了fiddler的AutoResponder功能。开篇介绍了,这个功能可以对请求进行拦截,并进行自定义处理。

首先选择“AutoResponder”选项卡,然后选择“Enable Automatic Responses”,选择“add”按钮来添加一个规则:

5

我这里选择如果请求包含“adjs/php ”(隐去前面的具体网址),则放弃(*drop)不去请求,当然这里的字符串是可以用正则表达式实现更为复杂的字符串匹配,另外,也可以选择其他匹配后的 处理方式,比如让本地模拟一个response,而不用真的到服务器请求了。

选择保存。再次按照步骤一,加载页面,cool,瞬间加载完毕,不影响浏览,广告少了一些,看来那两个导致加载慢的页面就是与广告相关的。

这种通过fiddler来过滤rul请求的功能,还可以实现更多功能,比如过滤掉对于音频、视频、图片等的请求,只获取浏览者需要的内容。

声明:我是此网站忠实的访问者。问题也许是我网络的问题也没准(比如说屏蔽着某些url的访问),我只是从浏览 者的角度,加快浏览速度而已。解铃还须系铃人,这个只是个临时方法,总不能每次都打开fiddler再访问网页吧?网站开发人员,才应该是使用 fiddler的最佳用户。

[转载]ASP.NET 编程:了解 IHttpHandler

mikel阅读(994)

[转载]了解 IHttpHandler – Luckdv – 博客园.

1 、概述

HttpHandlerProcessRequest

说明:HttpHandler是一个HTTP请求的真正处理中心。在HttpHandler容器中,ASP.NET Framework才调用HttpHandler的ProcessRequest成员方法来对这个HTTP请求进行真正的处理,真正地对客户端请求的服务 器页面做出编译和执行,并将处理过后的信息附加在HTTP请求信息流中再次返回到HttpModule中。

2、举例

以一个aspx页面为例,正是在HttpHandler这里,一个aspx页面才被系统处理解析,并将处理完成的结果继续经由HttpModule传递下 去,直至到达客户端。当然,对于aspx页面,ASP.NET Framework在默认情况下是交给System.Web.UI.PageHandlerFactory这个HttpHandlerFactory来处 理的。当一个HTTP请求到达这个HttpHandlerFactory时,HttpHandlerFactory会提供出一个HttpHandler容 器,交由这个HttpHandler容器来处理这个HTTP请求。 一个HTTP请求都是最终交给一个HttpHandler容器中的ProcessRequest方法来处理的。

3、HttpHandler

(1)实现HttpHandler,必须继承自IHttpHandler接口。下面是这个接口的定义:

using System;
namespace System.Web
{
    public interface IHttpHandler
    {
        //  其他Request是否可以使用IHttpHandler
        bool IsReusable { get; }
        //  处理HttpRequest
        void ProcessRequest(HttpContext context);
    }
}

(2)自定义HttpHandler

新建一个网站,default.aspx页面:default.aspx.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace WebApplication1
{
    public partial class _Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            Response.Write("<br/>来自Default.aspx页面<br/>");
        }
    }
}

新添一个类库MyHandler,添加一个类如下:

using System;
using System.Web;
using System.Web.SessionState;
namespace WebApplication1
{
    public class MyTestHandler : IHttpHandler, IRequiresSessionState
    {
        public bool IsReusable { get { return true; } }
        public void ProcessRequest(HttpContext context)
        {
            context.Response.Write("<h3><b>This is a HttpHandler Test</b></h3>");
            context.Session["Test"] = "<h3><span style=\"color:blue;\">在HttpHandler容器中调用Session</span></h3>";
            context.Response.Write(context.Session["Test"]);
        }
    }
}

(3)配置文件
在web.config文件的system.web节点下,添加:

      <add verb="*" path="*.aspx" type="WebApplication1.MyTestHandler, WebApplication1" />

(4)注意

<1>、.NET为asp.net提供了很多系统默认HttpHandler类,用来适应不同类型的HttpRequest。比如aspx,在machine.config中是这样定义的:
<add verb=”*” path=”*.aspx” type=”System.Web.UI.PageHandlerFactory”/>
这就说明遇到aspx的Request请求,asp.net会将其交给System.Web.UI.PageHandlerFactory的HttpHandler类来处理。
<2>、如果自己定义了新的HttpHandler,而且在web.config中指定,则系统只会使用这个新的HttpHandler,而不再使用原先默认的或者指定的.

4、HttpHandlerFactory

ASP.NET Framework实际不直接将相关的页面资源HTTP请求定位到一个其内部默认的IHttpHandler容器之上,而定位到了其内部默认的 IHttpHandler工厂上。IHttpHandler工厂的作用是对IHttpHandler容器进行调度和管理,这样做的优点是大大增强了系统的 负荷性,提升了效率。
(1)IHttpHandlerFactory接口
IHttpHandlerFactory接口包含两个方法:GetHandler方法返回实现IHttpHandler接口的类的实例,ReleaseHandler方法使工厂可以重用现有的处理程序实例。

using System;
using System.Web;
namespace System.Web.UI
{
    public class PageHandlerFactory : System.Web.IHttpHandlerFactory2, IHttpHandlerFactory
    {
        protected internal PageHandlerFactory();
        public virtual IHttpHandler GetHandler(HttpContext context, string requestType, string virtualPath, string path);
        public virtual void ReleaseHandler(IHttpHandler handler);
    }
}

(2) 实现一个简单的HttpHandler工厂
类库新添一个文件MyHandlerFactor.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace WebApplication1
{
    public class MyHandlerFactory : IHttpHandlerFactory
    {
        public IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated)
        {
            string fileName = url.Substring(url.LastIndexOf("/") + 1);
            string objName = fileName.Substring(0, fileName.IndexOf("."));
            string className = "WebApplication1." + objName;
            object objHandler = null;
            try
            {
                // 采用动态反射机制创建相应的IHttpHandler实现类。
                objHandler = Activator.CreateInstance(Type.GetType(className));
                context.Response.Write(className);
            }
            catch (Exception e)
            {
                throw new HttpException("工厂不能为类型" + objName + "创建实例。", e);
            }
            return (IHttpHandler)objHandler;
        }
        public void ReleaseHandler(IHttpHandler handler)
        {
        }
    }
    public class Handler1 : IHttpHandler
    {
        public bool IsReusable { get { return true; } }
        public void ProcessRequest(HttpContext context)
        {
            context.Response.Write("<br/>这是来自于MyHandlerFactory里的处理.<br/>");
            context.Response.Write("<h3>来自Handler1的信息.</h3>");
        }
    }
    public class Handler2 : IHttpHandler
    {
        public bool IsReusable { get { return true; } }
        public void ProcessRequest(HttpContext context)
        {
            context.Response.Write("<br/>这是来自于MyHandlerFactory里的处理.<br/>");
            context.Response.Write("<h3>来自Handler2的信息.</h3>");
        }
    }
}
   <add verb="*" path="Handler1.aspx" type="WebApplication1.MyHandlerFactory,WebApplication1"/>
   <add verb="*" path="Handler2.aspx" type="WebApplication1.MyHandlerFactory,WebApplication1"/>

到这里,针对Handler1.aspx和Handler2.aspx两个页面的http请求我们就通过HttpHandler工厂处理好了。

5、HttpHandler和HttpModule的区别

主要有两点:
(1)先后次序.先IHttpModule,后IHttpHandler,IHttpHandler处理结束后再交给IHttpModule;
(2)对请求的处理上:
IHttpModule是属于大小通吃类型,无论客户端请求的是什么文件,都会调用到它;例如aspx,html,rar的请求;
IHttpHandler则属于挑食类型,只有asp.net注册过的文件类型(例如aspx,ascx,asmx等等)才会轮到调用它。

download2

[转载]Android:Text中电话号码、网址自动链接

mikel阅读(914)

[转载]android—Text中电话号码、网址自动链接 – 莴笋炒肉 – 博客园.

假若TextView文本中有电话号码或者网址,我想通过点击电话号码或者网址就能实现打电话或者打开网页,Android中已经为我们提供这样的属性和方法进行设置,大体可以分为三种:

1、设置TextView的autoLink属性:他有几个值all、web、phone、email等。当文中有这几种类型的文本值时,点击它将进入网页、打电话或者email的activity,这是最简单的方法

2、在文本值直接添加链接

(1)例如在string.xml文件中:<string><a href=http://www.google.com>http://www.google.com</a> <a href=”tel:18600000001″>tel</a> </string>,同时设置TextView属性 setMovementMethod(LinkMovementMethod.getInstance());

(2)在代码中使用Hteml.fromHtml构建文本

代码

tv2.setText( Html.fromHtml("the google url: " + "<a href=\"http://www.google.com\">http://www.google.com</a><br/>" + "the telephone: " + "<a href=\"tel:18603045201\">18603045201</a>)" )); tv2.setMovementMethod(LinkMovementMethod.getInstance());

3、使用SpanableString指定某段字串为链接文本

代码

TextView tv3=(TextView)findViewById(R.id.tv3); SpannableString ss= new SpannableString("the google url: http://www.google.com 18600000001"); ss.setSpan(new URLSpan("http://www.google.com"), 16, 37, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); ss.setSpan(new URLSpan("tel:18603045201"), 38, 49, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); tv3.setText(ss); tv3.setMovementMethod(LinkMovementMethod.getInstance());

[转载]巧用Webbrowser实现网络数据采集

mikel阅读(1113)

[转载]新手入门:巧用Webbrowser实现网络数据采集 – 学院派的驴 – 博客园.

作者:finallyliuyu 出处:博客园(博文转载请标明作者和出处)

编者按:本系列文章给出的网络数据采集方法、思路、和框架并无工业化应用价值,但足以满足各高校实验室在实验阶段爬去语料库,获取网络资源的需求。

欢迎老鸟指点,但是此篇博文的定位是“写个菜鸟,新手的”所以禁止无厘头的疯狗式乱骂。

在上一篇文章:《巧用C# webbrowser实现动态网页爬虫机器人》 中,给出了一个综合利用webbrowser控件,MSHTML DOM,正则表达式,以及线程阻塞技术爬取动态网页链接的框架和方法。(注:所谓动态网页是指:目标网页的URL不能从当前网页中直接获得,而是要依赖 JavaScript重新定向和生成。这种动态网页,我们有的时候可以通过查看页面<script>元素的内容找到蛛丝马迹,当更多时候,生 成目标网页的URL的JS函数的源码不再当前网页,这时候就显得无能为力了)。《巧用C# webbrowser实现动态网页爬虫机器人一文中的框架方法也同样适用于静态网页,可以说这个框架方法在某种程度上具有一定的通用性。此篇博文是对上一篇博文的一个补充,实现的功能是:从一级索引页获取到二级索引页首页的链接,在二级索引页首页获取当前页面中指向内容页面的URL链接,并且进行翻页获取内容页源码,同时完成二级索引页首页到次页的翻页,并以此类推。(注意:索引页,内容页的定义请见:《巧用C# webbrowser实现动态网页爬虫机器人》)

网络数据采集是网络挖掘的一个重要步骤。属于预处理工作范畴。一般分为两步,网络信息采集(即下载网页源码)和网络信息解析(即对网页源码进行解析,提取出BOI (block of interest))。本文是对《巧用C# webbrowser实现动态网页爬虫机器人》的补充,完成网络信息采集功能。

下面给出关键模块代码:

文章实体

public class ArticlePage
{
public string title;
public string url;
public string rawtext;
public ArticlePage()
{
title
= string.Empty;
url
= string.Empty;
rawtext
= string.Empty;
}

}

信号变量定义

public bool mysignal1;//btnworkflow按钮是否被点击
public bool mysignal2;
public bool mysignal3;
public bool loading;//工作流按钮与webbrowser进行交互的通信按钮
public bool subloading;
public bool subloadingPer;
信号变量初始化

public Form1()
{
InitializeComponent();
mysignal1
= false;
mysignal2
= false;
mysignal3
= false;
loading
= true;
subloading
= true;
subloadingPer
= true;
}
webbrowserCompleted更新信号

private void webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
if (webBrowser1.ReadyState ==WebBrowserReadyState.Complete)
{
if (mysignal1)
{
if (!mysignal2&&!mysignal3)
{
loading
= false;
}
else
{
if (mysignal2&&!mysignal3)
{
subloading
= false;
}

if (mysignal3)
{
subloadingPer
= false;
}

}

}

}

}

实现自动爬虫的工作流

private void btnworkflow_Click(object sender, EventArgs e)
{
mysignal1
= true;
string dirCurPageUrl = string.Empty;//用于恢复目录页当前页的网页视图
List<ArticlePage> arListCurrentPage;
foreach (string s in issuesMap)
{

loading = true;
string tmpurl = s;
webBrowser1.Navigate(tmpurl);

while (loading == true)
{
Application.DoEvents();
}

arListCurrentPage = GetArticlePageInfoFromCurrentDirpage();
if (arListCurrentPage != null)
{
dirCurPageUrl
= webBrowser1.Url.ToString();
mysignal3
= true;
foreach ( ArticlePage ap in arListCurrentPage)
{
webBrowser1.Navigate(ap.url);
subloadingPer
= true;
while(subloadingPer)
{
Application.DoEvents();
}
ap.rawtext
= webBrowser1.DocumentText;
}
InsertTitleUrlToDataBase(arListCurrentPage);
mysignal3
= false;
webBrowser1.Navigate(dirCurPageUrl);
loading
= true;
while (loading == true)
{
Application.DoEvents();
}
}

mysignal2 = true;
while (AnchorNextPage())
{
subloading
= true;
while(subloading)
{
Application.DoEvents();

}
arListCurrentPage = GetArticlePageInfoFromCurrentDirpage();
if (arListCurrentPage != null)
{
dirCurPageUrl
= webBrowser1.Url.ToString();
mysignal3
= true;
foreach (ArticlePage ap in arListCurrentPage)
{
webBrowser1.Navigate(ap.url);
subloadingPer
= true;
while (subloadingPer)
{
Application.DoEvents();
}
ap.rawtext
= webBrowser1.DocumentText;
}
InsertTitleUrlToDataBase(arListCurrentPage);
mysignal3
= false;
webBrowser1.Navigate(dirCurPageUrl);
subloading
= true;
while (subloading)
{
Application.DoEvents();

}

}

}
mysignal2 = false;

//获得当前页面的下一页链接

}

}

数据库操作

private void InsertTitleUrlToDataBase(List<ArticlePage> arlist)
{
DataBaseManipulation dm
= new DataBaseManipulation();
string conStr = server=(local);database=xxxxx;uid=sa;pwd=xxx;
dm.ConstructConnection(conStr);
foreach (ArticlePage article in arlist)
{
dm.InsertToDataBase(article,
xxx);
}

}

数据库操作2

public void InsertToDataBase(ArticlePage article,string table )
{
//插入字符串
string SQLcommand=string.Format(insert  into {0}(ArticlePageUrl,ArticlePageTitle,ArticlePageSource)values(@ArticlePageUrl,@ArticlePageTitle,@ArticlePageSource),table);

//数据库参数构造与赋初值
SQLParameter ArticlePageTitle = new SQLParameter(@ArticlePageTitle, SqlDbType.VarChar, 400);
ArticlePageTitle.Value
= article.title;
SqlParameter ArticlePageUrl
= new SqlParameter(@ArticlePageUrl, SqlDbType.VarChar, 400);
ArticlePageUrl.Value
= article.url;
SqlParameter ArticlePageSource
= new SqlParameter(@ArticlePageSource, SqlDbType.Text);
ArticlePageSource.Value
= article.rawtext;
SqlCommand cmd
= new SqlCommand(sqlcommand, connection);
cmd.Parameters.Add(ArticlePageTitle);
cmd.Parameters.Add(ArticlePageUrl);
cmd.Parameters.Add(ArticlePageSource);

//打开数据库连接
OpenConnection();

try
{
//执行cmd操作
cmd.ExecuteNonQuery();
}
catch (System.Exception e)
{
//输出错误到记事本中
StreamWriter sw = new StreamWriter(D:\\myerror.txt, true, Encoding.Default);
sw.Write(e.Message);
sw.Close();
//一旦发生错误程序就停止运行,等待用户发现
Console.Read();

}

//关闭数据库连接
CloseConnection();

}

}
}

本地数据库(保存爬取的信息)视图:

小结与发散:笔者于本科毕设做过新闻类网页正文提取的课题,请见《新闻类网页正文提取系列》,并且从各大新闻门户网站的不同版面提取了四万余条新闻作为语料库,该语料库已经提供给网友下载,语料库说明以及下载地址见:《献给热衷于自然语言处理的业余爱好者的语料库》。 但是毕设当时,我对于动态网页提取方法并没有一个清晰的认识,所以可以提取的新闻版面上限制很大(必须是静态新闻网页才能提取)。有兴趣,并且对语料库有需求的网友,可以参考我的此篇博客,以及上一篇博客《巧用C# webbrowser实现动态网页爬虫机器人》,还有正文提取系列的一些博文,自己动手配置一个自动获取新闻语料库的小型爬虫。

[转载]介绍下Nupack如何在asp.net mvc 2中使用

mikel阅读(1146)

[转载]介绍下Nupack如何在asp.net mvc 2中使用 – 爱因斯坦的小脑 – 博客园.

Nupack是一个打包好的.net工具集,Nupack团队开发它是为了让开发人员能够很容易得救把第三方工具集成到你的项目中。

image

Package Management并不是一个新概念,在之前Unixm,Ruby等里面都有过这个概念。

关于Nupack的介绍之前在ScottGu的博客http://weblogs.ASP.NET/scottgu/archive/2010/10/06/announcing-nupack-asp-net-mvc-3-beta-and-webmatrix-beta-2.aspx

image

但是对于我们英语不大好的开发人员来说,看这个还是有点难度的。我会在这里从安装到使用,详细的介绍下。

很喜欢VS2010的Extension Manager Tool,直接使用它就可以把NuPack给安装好,当然了我们是说你用的是ASP.NET MVC 2,如果是是安装了ASP.NET MVC3 Preview那么你不需要再去安装一次Nupack,它已经自动安装好了。

如果是ASP.NET MVC2,别急,你可以通过Extension manager Tool来安装。

打开EMT,选择Online Gallery,在右上角的搜索框搜Nupack,:

image

因为我这里已经安装好了,你可以点击download来下载好,然后安装好。重启vs2010。

下面我给大家说明下如何使用Nupack。先创建一个ASP.NET MVC 2的项目:

image

1.如何打开Package Manager Console?

PMC默认不显示在VS中,你需要去View->Other windows->Package Manager Console这里选中它,它就会显示在VS下方。

image

2、如何使用NuPack命令

第一个命令是List-Package:,你直接在Package Manager Console下输入List-Package,就会看到NuPack中目前的所有第三方工具了,如下图:

image

如果你需要把某个第三方工具添加到你的项目中,只需要输入Add-Package XXX ,例如我们需要把’elmah’添加到项目中,只需在PMC中输入如下:

image

你如果不知道elach是干嘛的请去 google code上看一下这个open source项目的介绍。它主要是用来记录你的网站报错信息,可以把错误信息通过email,twitter等发送给你,也可以保存在数据库中。上面的操 作已经把这个第三方工具添加到你的项目中,同是还修改了web.config文件,也就是说连配置都给你自动改了。很不错。

image

现在试试是否elmah能够正常使用,我们先来随意输入一个url让系统报错我输入一个shit/shit。

image

然后访问elmah.axd看看是否错误日志已经记录下来了。

image

不错,已经记录下来了。。。。。

接下来我们试试添加NHibernate.linq看看还有什么神奇的功能。

image

哇,它会自己把需要用到的相关工具自己下载下来。

如果想移除NHibernate.linq你只要在控制台输入如下命令:

PM> Remove-Package NHibernate.Linq –RemoveDependencies
如果你不想使用Dos命令,你可以通过添加Package来直接搞定。
image
还有其他很多的功能,我希望后面我可以抽个时间个大家介绍,你们如果对这个感兴趣也可以自己研究下。不写啦今天。呵呵。
Cheers
Nick
要是有人需要这个项目的代码可以留言。下面可以输入邮件。。。其实代码也没什么的。

[转载]Excel超链接的应用-在Excel里输入身份证号,自动生成查找身份证信息的链接

mikel阅读(1259)

[转载]Excel超链接的应用-在Excel里输入身份证号,自动生成查找身份证信息的链接 – 耕者 – 博客园.

今天公司一位同事告诉我,她想把在Excel里输入的身份证号码设置超链接,直接点击就可以链接到网页里显示查找的信息,问题很突然,正常情况下我们都知道设置超链接时直接是把固定链接地址设置到超链接即可,而这里显然是要传递参数才行,就以http://www.ip138.com/ 的身份证查询链接为例:http://qq.ip138.com/idsearch/index.asp?action=idcard&userid=123456789012345678&B1=%B2%E9+%D1%AF 其中123456789012345678就是要传递的参数(这里表示身份证号码),如何完成这一自动填写参数过程呢,首先我想到了两个公式:超链接HYPERLINK和文本截取SUBSTITUTE

公式语法:

HYPERLINK(link_location, friendly_name)
link_localtion:链接地址

friendly_name:为单元格内要显示的信息

SUBSTITUTE(text,old_text,new_text,instance_num)

Text:为需要替换其中字符的文本,或对含有文本的单元格的引用

Old_text : 为需要替换的旧文本

New_text :用于替换 old_text 的文本

Instance_num :为一数值,用来指定以 new_text 替换第几次出现的 old_text。如果指定了 instance_num,则只有满足要求的 old_text 被替换;否则  将用 new_text 替换 Text 中出现的所有 old_text

我的示例:

假如我在A1单元格里输入身份证号码”123456789012345678″(注意要用文本格式),将D1单元格最为自动生成的超链接:公式如

” =HYPERLINK(SUBSTITUTE(“http://qq.ip138.com/idsearch/index.asp?action=idcard&userid=000000000000000000&B1=%B2%E9+D1%AF”,”000000000000000000″,A1,1),A1) ” ,再D1单元格的数据将自动显示为123456789012345678的超链接,单击即可进入网页查询身份信息

[转载]PDC 2010:C#与Visual Basic的未来(中)

mikel阅读(1139)

[转载]PDC 2010:C#与Visual Basic的未来(中) – 老赵点滴 – 追求编程之美.

PDC 2010:C#与Visual Basic的未来(中)

2010-10-31 21:49 by 老赵, 382 visits

前几天在PDC 2010会议上Anders Hejlsberg发表了一场名为“The Future of C# and Visual Basic”的演说,谈论了未来C#和VB中最为重要的两个特性:“异步(Async)”及“编译器即服务(Compiler as a Service)”。我现在对这场演讲进行总结,但不会像上次《编程语言的发展趋势及未来方向》那样逐句翻译,而是以Anders的角度使用一种简捷合适的方式表述其完整内容。上一篇Anders讲述了async和await的使用方式,而这篇则是对这两个关键字的实现及效果作更进一步的解释。

异步方法的目标,是为了让代码与同步方法保持一致。微软要让代码充斥着回调函数,混乱不堪,它们完全不是逻辑上你想做的事情。可能您的代码中包含着一个核心模型,你也已经实现了,只是您现在想把它的执行过程变得异步化。您自己就可以享受到这一点。

与我们之前做的一些扩展一样,工作分为语言和框架两部分。语言方面的异步工作是基于Task<T>的,我们会围绕着 Task<T>扩展框架,将它作为异步模型的核心。事实上,从Begin/End,或是基于事件的异步模型进行扩展往往只需要一两行封装的代 码,于是您也可以得到自己的Task<T>模型。

而在语言方面,我们添加了两个新的关键字。一个是async关键字,用于把方法标记为异步。还有一个是await方法,用于等待异步工作完成,或者说是把控制权交换给调用方继续执行其他工作。这两个功能在C#和VB种均有体现。

那么什么是Task<T>呢?它表现的是一个“后续会继续进行的操作”,这可以是许多东西,Task<T>并不做任何限制,例如是一个异步I/O,后台工作线程等等,甚至可以是UI上的一个按钮,在用户点击之后任务就结束了。

Task<T>的优势在于,它使用一个对象封装了整个概念,您可以查询其结果或是状态,或是这个任务所引发的异常。您可以用它来构造一个可组合的异步模型,这正式我们目前的异步编程模型所不足的地方。

此外,它还提供了一个可组合的回调模型,您可以对一个任务指定说,在它结束之后执行另外一段代码,然后还可以对这个新的任务继续进行设定。这便构造 出一个完整的逻辑流,框架会自行帮你完成这些工作。事实上await操作符便会自动把您的逻辑改写成这样的代码,它将您从Lambda表达式及回调函数中 的逻辑里解放了出来,一切都交给编译器去做了。您可能会有些疑惑,不过其实这些都是编译器所擅长的事情。

由于我们统一了异步模型,我们就可以在此之上构建组合工具。例如WhenAll,它接受一系列的Task对象,并在全部结束之后返回所有结果。还有WhenAny,则等待第一个完成的任务,返回其结果。我们还有Delay,可以等待一段时间,但不占用任何资源。

沿着这个过程走一遍可能就会清晰一些。这里有个例子,一个异步方法调用另一个异步方法。我们假设这是在UI线程上执行的,消息会一个一个发送至UI线程上。

好,有人调用了DoWorkAsync,于是出现了一些任务。

DoWorkAsync的第一件事,是调用了ProcessFeedAsync。

ProcessFeedAsync方法是一个异步方法,所以它做的第一件事是构造一个表示任务的Task对象。

然后它调用了DownloadFeedAsync,这会创建另一个Task对象。然后,我们遇上了await操作符,这意味着ProcessFeedAsync后面的部分,将作为DownloadFeedAsync完成后的回调函数/continuation里的工作。

于是任务返回至DoWorkAsync,我们得到了t1这个对象。

同样的过程会再次出现,是为t2。

然后便调用了Task.WhenAll,这会创建一个新任务,表示前两个任务全部完成。于是这里的await操作符表示接下去的代码会在前两个任务完成后再继续下去。此时控制权便还给了DoWorkAsync的调用者,不会对线程造成负担。

在未来某一时刻t1和t2会执行完,我们假设t2先结束。此时它会说:我完成了,执行回调函数/continuation吧。

于是它会和发起线程的SynchronizationContext交互,给UI线程发一个消息,让后续任务在UI线程上继续执行──您的代码不用 关注这些。现在代码运行至SaveDocAsync上了,这是另外一个异步任务。await让代码在这里返回,线程又可以执行目前还未结束的任务了。

于是SaveDocAsync任务完成了,UI线程又获得了一个消息执行后续工作。

此时任务便到达了ProcessFeedAsync的末尾,于是t2任务结束了。

继续等待,上面的过程会再次出现,最终t1也结束了。

当t1和t2完成以后,最后DoWorkAsync任务也终于结束了。可以看到,我们逻辑流程,无论是循环还是异常捕获都是同步的,但是其中的执行过程完全是异步的。

但是这又是如何实现的?我不会在这里说太细,这又是个完整的话题了。这里有一个例子,是一个异步方法,它会调用并await另一个异步方法。

而编译器则最终则生成类似于这样的代码。我只会提几点,首先,这是个状态机,编译器构造的其实就是个状态机,例如迭代器就是个状态机,事实上这里编译器的工作和yield之余迭代器的重写本质上没有太大区别。

其次就是关于任务的执行和等待,假如在等待时任务已经完成了,那么其实您是在同步地执行后续代码。我们没有必要交还控制,反正已经完成了,我们不妨 就直接进行下去了。await有自己的模式,会决定这一任务是同步还是异步地执行。对于同步执行的任务,一切就继续执行下去了,直到某个需要异步执行的地 方,便把控制权交还给调用方。

那么我们再来看一下异步之于Web服务的意义。这里有个ASP.NET页面,它会向数据库里获取许多RSS地址,然后下载到本地并解析:

private void ProcessData()
{
    // ...

    var urls = new List<string>();
    using (var conn = new SqlConnection(connectionString))
    {
        conn.Open();
        var cmd = new SqlCommand("GetUserFeeds", conn);
        cmd.CommandType = CommandType.StoredProcedure;
        cmd.Parameters.AddWithValue("@UserID", user);
        using (var reader = cmd.ExecuteReader())
        {
            while (reader.Read()) urls.Add(reader["FeedURL"].ToString());
        }
    }

    var feeds = (from url in urls select CreateWebClient().DownloadString(url)).ToArray();

    // ...
}

这里用到了DownloadString这个同步下载数据的方法。执行下来大约要花费1秒多的时间。这里我不再演示令人痛苦的异步写法了,你必须在 Page_Load和Page_PreRender各写一些逻辑,注册一些异步工作,或者就要启用一些后台线程,但这又会影响后台的线程池,对系统的表现 会带来影响。

现在我来演示一些简单的异步化工作:

private async void ProcessData()
{
    // ...

    var urls = new List<string>();
    using (var conn = new SqlConnection(connectionString))
    {
        conn.Open();
        var cmd = new SqlCommand("GetUserFeeds", conn);
        cmd.CommandType = CommandType.StoredProcedure;
        cmd.Parameters.AddWithValue("@UserID", user);
        using (var reader = await cmd.ExecuteReaderAsync())
        {
            while (reader.Read()) urls.Add(reader["FeedURL"].ToString());
        }
    }

    var feeds = await TaskEx.WhenAll(
        from url in urls select CreateWebClient().DownloadStringTaskAsync(url));

    // ...
}

我们将DownloadString修改为DownloadStringTaskAsync,这样LINQ返回的就是一系列表示下载任务的Task 对象,然后使用await及WhenAll等待它们全部完成。数据库查询也可以如此。这就是所有我们要做的事情。如今页面的执行效率有了很明显的提高。使 用这个做法,我们可以很轻松地提高Web系统的伸缩能力。如今我们需要调用很多互相独立的服务的情况越来越多了,异步方法对此有很大帮助。

如今的异步场景有许多种,例如在后台执行一个计算任务,这是基于CPU的异步,还有基于网络或I/O的异步任务。这些都能用Task来表示出来,因 为Task表示的就是未来会完成的异步任务。此外,有了async和.NET框架,我们则出现了另外一种任务,既基于某些任务组合而成的异步任务。这也就 是async方法所体现出的异步任务,它可以让你使用传统的语句来构造异步执行过程。

例如有这么一个场景:获取链接,根据链接下载Youtube视频,根据下载到的视频创建mashup并组合起来。在执行这些工作的时候,我们也希望UI可以响应用户操作。

而要完成这些工作,代码可能只需要这么简单,完全就像同步代码一样。而这里也体现了多种异步任务:ScrapeYoutubeAsync是网络密集 型任务,然后同时下载两个视频并等待它们结束。然后MashupVideosAsync是CPU密集型任务,然后最后则是I/O密集型的的 SaveAsync操作。对于异常处理来说,我们可以简捷地使用一个try…catch,就像传统编程那样。

总结一下,一个异步方法可以让代码和同步实现一样简单,并统一了计算、网络及I/O的异步化。这可以用来创建高度伸缩的服务器程序,自然还有响应度高的UI程序。

在演讲的末尾,我会给出Visual Studio Async CTP的下载链接,我很乐于得到大家的反馈。

相关文章