[转载]使用 ExtJS 实现 ASP.NET MVC 2 客户端验证

mikel阅读(1141)

[转载]使用 ExtJS 实现 ASP.NET MVC 2 客户端验证 – Beginor – 博客园.

ASP.NET MVC 2 中, 客户端表单验证信息不再是直接调用微软自己提供的方法, 而是将客户端表单验证的元数据放到了一个变量 (window.mvcClientValidationMetadata) 之中, 为实现第三方的客户端验证提供了可能, 由于工作中大量的使用到了 ExtJS ,于是抽时间用 ExtJS 实现了 ASP.NET MVC 2 客户端验证机制,主要有如下特点:

  • 只依赖 Ext core 即可使用,不需要完整版本的 ExtJS, 当然,如果有完整版 ExtJS 的话, 还可以调用 Ext.form.VTypes 的表单验证方法;
  • 使用方法完全遵循 ASP.NET MVC 2 提供的客户端验证机制,实现了默认的 required 、regularExpression 、 number 、range 、stringLength 客户端验证函数;
  • 可以根据 ASP.NET MVC 2 提供的验证扩展机制进行相应的扩展。

使用方法如下:

1、在要进行验证的 Model 上添加验证标记, 代码如下:

01 public class RegisterModel {
02
03 [Required(ErrorMessage = "用户名必须填写!")]
04 [DisplayName("用户名:")]
05 public string UserName {
06 get;
07 set;
08 }
09
10 [Required(ErrorMessage = "密码必须 填写!")]
11 [DisplayName("密码:")]
12 [DataType(DataType.Password)]
13 public string Password {
14 get;
15 set;
16 }
17
18 [Required(ErrorMessage = "邮箱必须 填写")]
19 [RegularExpression("", ErrorMessage = "邮件格式不正确!")]
20 [DisplayName("邮箱:")]
21 public string Email {
22 get;
23 set;
24 }
25
26 [Range(0, 100, ErrorMessage = " 年龄必须在1~100之间!")]
27 [DisplayName("年龄:")]
28 [DefaultValue(20)]
29 public int Age {
30 get;
31 set;
32 }
33
34 }

2、在 View 中添加下面的代码,除了要使用 ExtJS 的脚本之外, 与普通的 View 没有什么区别, 代码如下:

01 <!-- 先引入ExtJS -->
02 <link rel="Stylesheet" type="text/css" href="http://extjs.cachefly.net/ext-3.2.1/resources/css/ext-all.css">
04 <script src="http://extjs.cachefly.net/ext-3.2.1/ext-all.js" type="text/JavaScript"></script>
05 <script src="/Assets/Scripts/Ext.ux.mvc.validation.js" type="text/JavaScript"></script>
06 <link href="/Assets/Site.css" rel="stylesheet" type="text/css">;
07
08 <!-- 下面开始表单 -->
09 <h2>ExtJS 实现 ASP.NET MVC 2 客户端验证</h2>
10 <% Html.EnableClientValidation(); %>;
11 <%= Html.ValidationSummary(true, "输入信息不完整,无法完成注册。") %>
12 <% using (Html.BeginForm()) { %>
13 <%= Html.EditorForModel() %>
14 <input value="注册" type="submit">
15 <input value="取消" type="reset">
16 <% } %>;

运行效果如下图所示:
2010-06-05_150039

如果要做扩展自定义验证的话,需要做完成下面两部分:

1、参考 msdn 文档,添加服务端验证扩展, 代码如下:

01 [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
02 public class CustomAttribute : ValidationAttribute {
03
04 public override bool IsValid(object value) {
05 var val = value as string;
06 if (string.IsNullOrEmpty(val)) {
07 return false;
08 }
09 return val.Equals("Hello,world!", StringComparison.OrdinalIgnoreCase);
10 }
11 }
12
13 public class CustomValidator : DataAnnotationsModelValidator<customattribute> {
14
15 public CustomValidator(ModelMetadata metadata, ControllerContext context, CustomAttribute attribute)
16 : base(metadata, context, attribute) {
17 }
18
19 public override IEnumerable<modelclientvalidationrule> GetClientValidationRules() {
20 return new[] {
21 new ModelClientValidationRule {
22 ErrorMessage = "输入: Hello,world!",
23 ValidationType = "custom"
24 }
25 };
26 }
27 }
28 </modelclientvalidationrule></customattribute>

2、添加对应的客户端验证实现,代码如下:

1 Ext.apply(Ext.ux.mvc.VTypes, {
2 custom: function(val, param) {
3 return val.toLowerCase() == 'hello,world!';
4 }
5 });

3、在 Model 上添加属性,使用扩展验证,代码如下:

4、在 App_Start 注册该扩展,代码如下:

1 DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(CustomAttribute), typeof(CustomValidator));

5、添加扩展之后的运行效果如下图:
2010-06-05_150121

如果你工作中也用到了 ExtJS 和 ASP.NET MVC 2 的话,可以下载这个文件来 尝试一下。

[转载]实用的不间断滚动图效果,具备良好兼容

mikel阅读(870)

[转载][分享] 实用的不间断滚动图效果,具备良好兼容; – 风帆 – 博客园.

网上有不少图片滚动的效果代码,但大多兼容性不好,或者结构混 乱,xhtml,css,js 均没有得到良好的优化,下面介绍一个方法;基本上实现结构跟行为相分离,两者的联系只需要一个
id就可以,使用起 来很方便,而且同一个js代码可以实现在本页面多个滚动图效果,之间互不干扰,

1.xhtml

<div class="demo" id="demo1"> <table cellpadding="0" cellspacing="0"> <tr> <td><ul> <li><img src="img.jpg"><br> 滚动图1</li> <li><img src="img.jpg"><br> 滚动图2</li> <li><img src="img.jpg"><br> 滚动图3</li> <li><img src="img.jpg"><br> 滚动图4</li> </ul></td> </tr> </table> </div>
<!--第二个滚动图开始--> <div class="demo" id="demo2"> <table cellpadding="0" cellspacing="0"> <tr> <td><ul> <li><img src="img.jpg"><br> 滚动图1</li> <li><img src="img.jpg"><br> 滚动图2</li> <li><img src="img.jpg"><br> 滚动图3</li> <li><img src="img.jpg"><br> 滚动图4</li> </ul></td> </tr> </table> </div>

2.css

ul,li,img,td{font-size:12px;list-style-type:none;text-align:center;margin:0;padding:0;} .demo{width:230px;margin-bottom:8px;height:172px;overflow:hidden;} .demo ul{width:408px;clear:both;} .demo li{width:102px;float:left;text-align:center;} .demo img{margin-bottom:8px;}

3.js

function startmarquee(lh,speed,delay,index){ var o=document.getElementById("demo"+index); var o_td=o.getElementsByTagName("td")[0]; var str=o_td.innerHTML; var newtd=document.createElement("td"); newtd.innerHTML=str; o_td.parentNode.appendChild(newtd); var t; var p=false; o.onmouseover=function(){p=true;} o.onmouseout=function() {p=false;} function start(){ t=setInterval(Marquee,speed); if(!p){o.scrollLeft += 3;} } function Marquee(){ if(o_td.offsetWidth-o.scrollLeft<=0) o.scrollLeft-=o_td.offsetWidth; else{ if(o.scrollLeft%lh!=0){ o.scrollLeft+= 3 }else{clearInterval(t); setTimeout(start,delay);} } } setTimeout(start,delay); } startmarquee(102,1,1500,1);//图片间停式滚动 startmarquee(102,30,1,2);//图片不间断滚动

测试代码:js需要在html文件加载完成后执行,才能正常运行,所以记 得将js放到div之后;图片滚动跟文字滚动实现原理上大同小异,关于此篇更多js代码的解释,请参看我的另一篇博客

浅析文字滚动效果

[转载]CLR类加载简述与OSGi.NET插件平台类加载机制简述

mikel阅读(1075)

[转载]CLR类加载简述与OSGi.NET插件平台类加载机制简述 – 道法自然 – 博客园.

一个插件平台除了需要考虑插件的结构、插件位置、插件类型空间、插件依赖、插件通讯、插件多版本支持、插件国际化等插件所需的基本要 素之外,还需要考虑一个开发人员如何开发、调试和部署插件。本文简要描述了插件类型空间相关的知识——CLR Loader、CLR Loader VS Java ClassLoader和插件的类型空间及类加载机制实现。
1 CLR Loader
关 于CLR加载器的详细描述可以查看《Essential .NET, Volume 1: The Common Language Runtime》,也可以下载我翻译的关于CLR部分的文档“CLR加载器/Files/baihmpgy/CRL加载器.rar”。在这里我将描述一下书中没有说明的关于加载器的一 些信息,这些信息包括:(1)何时触发类加载;(2)LoadFile的妙用及缺陷;(3)AppDomain.AssemblyResolve和 AssemblyLoad事件。
在我目前认知情况下,我知道类加载发生在:A)CLR执行线程进入 一个方法之前会触发JIT对方法进行编译并加载这个方法所需要的类型;B)调用Type.GetType等系统方法也会触发类型加载。为了让读者更好了解 到类加载时机,我编写了一个Sample。该Sample的目录结构如下:
ClassLoaderTesting.exe
ClassLibrary1.dll
ClassLibrary2.dll
lib<directory>
—CL3<directory>
——–ClassLibrary.dll
你 可以点击此处/Files/baihmpgy/ClassLoaderTesting.rar下载Sample源码,在 Program.cs的Main方法你可以看到类加载实验,AssemblyLoad和AssemblyResolve事件发生的时机。
LoadFile 和LoadFrome的区别在于LoadFile每次加载一个程序集的时候,不管目前AppDomain是否已经加载了该程序集,它都会加载一次。此 外,LoadFile不会加载目标程序集引用的程序集。它为我们提供了加载多版本程序集支持且可以做到按需加载一个程序集依赖的所有程序集。当然,目前而 言,我所知的LoadFile的一个最大缺点,就是每次加载一个程序集后,它会Lock住程序集文件。这个缺点在支持插件调试时非常突出。
AssemblyResolve 事件在CLR类加载器无法加载到所需类型时触发,它赋予了我们自定义类型加载的扩展,相反AssemblyLoad事件则发生在程序集被加载时。你可以通 过调试ClassLoaderTesting这个项目来检查类型何时加载以及这两个事件何事触发。
2 CLR Loader Vs Java ClassLoader
这 个问题比较古老了,已经有人比较过CLR Loader和Java ClassLoader。不过,要想公平公正的进行比较还真不容易,这就像公正比较Java和C#,J2EE和.NET一样。前一段时间,就有很多人在批 Java。在比较CLR Loader和Java ClassLoader之前,先说几句废话。我先声明一下,我是一个C#程序员,也接触过Java。对Java类加载机制、Flex类加载机制有一定的实 践。我体会到一些.NET平台相对于Java平台所不具备的东西,而这些差距除了.NET只支持Windows和Linux之外,还有就是.NET缺少的 开放规范和缺少灵活的类加载机制。众所周知,Java有一堆的规范,比如JDBC、JMS、JMX、JNDI、JTX等等,当然,也有基于这些规范的各种 Providers,基于Java平台我们可以有很多挑选的余地,并且可以从这些高端的开放规范中学习到很多的知识,.NET平台就很少有开放规范了,如 果你非得说C#语言规范的话,那也算,:)。那些标榜“C#比Java绝对好”结论相关的文章我一般都懒得回复,因为这没有任何意义,我只想根据实际需要 选择更好的,而不是片面的排斥。要知道,现在用Java的程序员可是C#的几倍,而且Java已经被认为是云计算的标准开发语言(插一句,我不幸的是用 C#构建了一个SaaS引擎,深受客户的质疑),存在即合理。
言归正传,我将在 http://blog.joycode.com/junfeng/archive/2004/04/10/18901.joy这篇文章之上谈一下二者。 在Java,类是通过ClassLoader和类型全程来标识的,这意味着一个ClassLoader不能加载重复的类型,但是两个 ClassLoader可以加载同一个名称的类型。不过这时候,被加载的这两个类型创建的实例就是隶属于不同类型了。据我说知,Java类加载触发有两种 情况:A)JVM执行过程中;B)Class.forName。JVM在执行字节码的过程中,碰到一个未知类型后,会从当前线程获取对应的 ClassLoader,然后使用ClassLoader加载类型。ClassLoader加载类型采用一种层级方式,它首先先调用父类加载器进行加载, 如果加载不到才使用当前线程的ClassLoader进行加载。我们可以通过重写ClassLoader对JVM类型加载进行扩展。著名的Eclipse 的OSGi内核——Equniox就是通过重写ClassLoader自定义了一个插件类型空间的类型加载器。Class.forName则是显式调用当 前线程类加载器加载类型的方法了。很多Java组件框架都是通过扩展ClassLoader来实现模块化技术的,比如JBoss和NetBeans。
CLR 加载器概念,我们接触的机会很少,相反,我们对Assembly.Load/LoadFrom/LoadFile、Type.GetType倒是经常碰到 了,你可以通过《Essential .NET, Volume 1: The Common Language Runtime》该书深入理解CLR加载器。和Java ClassLoader相比,它的缺点主要有:(1)扩展性差;(2)加载的类型卸载的唯一方式是随着AppDomain一起被卸载。扩展性差表现在,实 现CLR自定义类型加载只能通过AppDomain.AssemblyResolve来实现(还有一种方法,就是可以通过.config文件来配置类型加 载,但这种方法是静态的,我没有把它当成一种方法),Assembly.Load/LoadFrom/LoadFile这些方法并不能实现对CLR加载器 进行扩展,它们只是一些补充,原因很简单,在执行“Class3 cls = new Class3();”时,Class3只能是由CLR来加载的。第二个缺点,很明显,一个类型一旦被加载了,它是无法卸载的,因为在C#,我们在不使用低 性能和跨进程调用的AppDomain下是无法实现一个支持动态更新的插件平台。
我的结 论:Java ClassLoader简单优雅、功能强大、非常灵活,但对多版本不提供支持;CLR Loader非常简单、支持多版本、但不太灵活且扩展性稍差。
3 OSGi.NET BundleLoader
OSGi.NET 是OSGi规范移植到.NET的实现。OSGi是一个基于Java的动态模块化系统的规范。它提供了模块化与插件化、面向服务、安全与隔离和模块扩展支持 的功能。由于OSGi具有规范的模块化与插件化定义,且已经经过了Eclipse IDE考验,我们便制定了基于C#的OSGi.NET开放服务规范并基于C#开发实现。
在设计 OSGi.NET过程中,模块层花费了大量的设计和开发的时间。我从易用性、类型空间独立性、类加载性能等方面考了,设计了一个几乎和OSGi模块类加载 器一样优雅的插件类型加载机制。它类似OSGi实现了一个插件的独立类型空间,在OSGi.NET中,一个插件能加载的类型由插件与子插件本地程序集、插 件与子插件依赖程序集、插件动态依赖程序集组成,通俗的讲,一个插件只能从本地和依赖的程序集加载所需的类型。这种方式使得多版本程序集支持也变得可能。 OSGi.NET插件类型加载由BundleLoader即插件类加载器来实现,其实现原理和OSGi类似。
不 过,为了实现每一个插件独立的类型空间,我们需要对CLR加载器进行扩展,其方式如下:
1 AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(DomainAssemblyResolve);
2
3 Assembly DomainAssemblyResolve(object sender, ResolveEventArgs args)
4 {
5 string resolvingAsmName = args.Name;
6 AssemblyName name = new AssemblyName(resolvingAsmName); //获取程序集全名。
7 Assembly asm = SearchAssemblyFromAllBundles(name); //从所有Bundle的程序集中查找匹配的程序集并加载。
8
9 return asm;
10 }
到 目前为止我对OSGi.NET的插件化支持还算满意,但限于个人对Java ClassLoader、CLR Loader和OSGi等精深知识理解还不够深入,如有错误,欢迎指正。

Creative  Commons License

本文基于Creative Commons Attribution 2.5 China Mainland License发布,欢迎转载,演绎或用于商业目的,但是必 须保留本文的署名道法自然(包含链接)。如您有任何疑问或者授权方面的协商,请给我留言。

[转载]深入解析Hashtable、Dictionary、SortedDictionary、SortedList

mikel阅读(1222)

[转载]深入解析Hashtable、Dictionary、SortedDictionary、SortedList – 木子博客 – 博客园.

在《在线用户 实体缓存解决方案》方案中使用Dictionary来存储,评论里同事说SortedDictionary采用二分法查找比Dictionary快,于是我们都做了测试,最后发现 Dictionary是比SortedDictionary快的,前者 用的是Hash算法,而后者是RB-Tree算法

于是想深入地分析如题的4个字典的原理。

我们先看Hashtable

MSDN的解释:表示键/值对的集合,这些键/值对根据键的哈希代码进行组 织。

Hash算法是把任意长度的输入(又叫做预映射, pre-image),通过散列算法,变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不 同的输入可能会散列成相同的输出,而不可能从散列值来唯一的确定输入值。

Hashtable 对象由包含集合元素的存储桶组 成。存储桶是 Hashtable 中各元素的虚拟子组,与大多数集合中进行的搜索和检索相比,存储桶 可令搜索和检索更为便捷。每一存储桶都与一个哈希代码关联,该哈希代码是使用哈希函数生成的并基于该元素的键。

Hashtable 类默认的装填因子是 1.0,但实际上它默认的装填因子是 0.72。所有从构造函数输入的装填因子,Hashtable 类内部都会将其乘以0.72。这是一个要求苛刻的数字, 某些时刻将装填因子增减 0.01, 可能你的 Hashtable 存取效率就提高或降低了 50%,其原因是装填因子决定散列表容量,而散列表容量又影响 Key 的冲突几率,进而影响性能。0.72 是 Microsoft经过大量实验得出的一个比较平衡的值。

我们看Hashtable的一些源码:

Hashtable .ctor

public Hashtable() : this(0, (float) 1f)
{
}
public Hashtable(int capacity, float loadFactor)
{
if (capacity < 0)
{
throw new ArgumentOutOfRangeException(capacity, Environment.GetResourceString(ArgumentOutOfRange_NeedNonNegNum));
}
if ((loadFactor < 0.1f) || (loadFactor > 1f))
{
throw new ArgumentOutOfRangeException(loadFactor, Environment.GetResourceString(ArgumentOutOfRange_HashtableLoadFactor, new object[] { 0.1, 1.0 }));
}
this.loadFactor = 0.72f * loadFactor;
double num = ((float) capacity) / this.loadFactor;
if (num > 2147483647.0)
{
throw new ArgumentException(Environment.GetResourceString(Arg_HTCapacityOverflow));
}
int num2 = (num > 11.0) ? HashHelpers.GetPrime((int) num) : 11;
this.buckets = new bucket[num2];
this.loadsize = (int) (this.loadFactor * num2);
this.isWriterInProgress = false;
}

Hashtable 扩容是个耗时非常惊人的内部操作,它之所以写入效率仅为读取效率的 1/10 数量级,频繁的扩容是一个因素。当进行扩容时,散列表内部要重新 new 一个更大的数组,然后把原来数组的内容拷贝到新数组,并进行重新散列。如何 new这个更大的数组也有讲究。散列表的初始容量一般来讲是个素数。当扩容时,新数组的大小会设置成原数组双倍大小的相近的一个素数。

Hashtable expand

private void expand()
{
int prime = HashHelpers.GetPrime(this.buckets.Length * 2);
this.rehash(prime);
}
private void rehash(int newsize)
{
this.occupancy = 0;
Hashtable.bucket[] newBuckets
= new Hashtable.bucket[newsize];
for (int i = 0; i < this.buckets.Length; i++)
{
Hashtable.bucket bucket
= this.buckets[i];
if ((bucket.key != null) && (bucket.key != this.buckets))
{
this.putEntry(newBuckets, bucket.key, bucket.val, bucket.hash_coll & 0x7fffffff);
}
}
Thread.BeginCriticalRegion();
this.isWriterInProgress = true;
this.buckets = newBuckets;
this.loadsize = (int) (this.loadFactor * newsize);
this.UpdateVersion();
this.isWriterInProgress = false;
Thread.EndCriticalRegion();
}

HashTable 数据结构存在问题:空间利用率偏低、受填充因子影响大、扩容时所有的数据需要重新进行散列计算。虽然Hash具有O(1)的数据检索效率,但它空间开销却 通常很大,是以空间换取时间。所以Hashtable适用于读取操作频繁,写入操作很少的操作类型。

Dictionary<K, V> 也是用的Hash算法,通过数组实现多条链式结构。不过它是采用分离链接散列法。采用分离链接散列法不受到装填因子的影响,扩容时原有数据不需要重新进行 散列计算。

采用分离链接法的 Dictionary<TKey, TValue> 会在内部维护一个链表数组。对于这个链表数组 L0,L1,…,LM-1, 散列函数将告诉我们应当把元素 X 插入到链表的什么位置。然后在 find 操作时告诉我们哪一个表中包含了 X。 这种方法的思想在于:尽管搜索一个链表是线性操作,但如果表足够小,搜索非常快(事实也的确如此,同时这也是查找,插入,删除等操作并非总是 O(1) 的原因)。特别是,它不受装填因子的限制。
这种情况下,常见的装填因子是 1.0。更低的装填因子并不能明显的提高性能,但却需要更多的额外空间。

Dictionary .ctor

public Dictionary() : this(0, null)
{
}
public Dictionary(int capacity, IEqualityComparer<TKey> comparer)
{
if (capacity < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity);
}
if (capacity > 0)
{
this.Initialize(capacity);
}
if (comparer == null)
{
comparer = EqualityComparer<TKey>.Default;
}
this.comparer = comparer;
}
private void Resize()
{
int prime = HashHelpers.GetPrime(this.count * 2);
int[] numArray = new int[prime];
for (int i = 0; i < numArray.Length; i++)
{
numArray[i] = 1;
}
Entry<TKey, TValue>[] destinationArray = new Entry<TKey, TValue>[prime];
Array.Copy(this.entries, 0, destinationArray, 0, this.count);
for (int j = 0; j < this.count; j++)
{
int index = destinationArray[j].hashCode % prime;
destinationArray[j].next = numArray[index];
numArray[index] = j;
}
this.buckets = numArray;
this.entries = destinationArray;
}

Dictionary 的插入算法:1、计算key的hash值,并且找到buckets中目标桶的链首索引,2、从链上依次查找是否key已经保存,3、如果没有的话,判断是 否存在freeList,4、如果存在freeList,从freeList上摘下结点保存数据,否则追加在count位置上。

Dictionary Add

private void Insert(TKey key, TValue value, bool add)
{
int freeList;
if (key == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
}
if (this.buckets == null)
{
this.Initialize(0);
}
int num = this.comparer.GetHashCode(key) & 0x7fffffff;
int index = num % this.buckets.Length;
for (int i = this.buckets[index]; i >= 0; i = this.entries[i].next)
{
if ((this.entries[i].hashCode == num) && this.comparer.Equals(this.entries[i].key, key))
{
if (add)
{
ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_AddingDuplicate);
}
this.entries[i].value = value;
this.version++;
return;
}
}
if (this.freeCount > 0)
{
freeList
= this.freeList;
this.freeList = this.entries[freeList].next;
this.freeCount;
}
else
{
if (this.count == this.entries.Length)
{
this.Resize();
index
= num % this.buckets.Length;
}
freeList
= this.count;
this.count++;
}
this.entries[freeList].hashCode = num;
this.entries[freeList].next = this.buckets[index];
this.entries[freeList].key = key;
this.entries[freeList].value = value;
this.buckets[index] = freeList;
this.version++;
}

buckets数组 保存所有数据链的链首,Buckets[i]表示在桶i中数据链的链首元素。entries结构体数组用于保存实际的数据,通过next值作为链式结构的 向后索引。删除的数据空间会被串入到freeList链表的首部,当再次插入数据时,会首先查找freeList链表,以提高查找entries中空闲数 据项位置的效率。在枚举器中,枚举顺序为entries数组的下标递增顺序。

Dictionary Remove

public bool Remove(TKey key)
{
if (key == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
}
if (this.buckets != null)
{
int num = this.comparer.GetHashCode(key) & 0x7fffffff;
int index = num % this.buckets.Length;
int num3 = 1;
for (int i = this.buckets[index]; i >= 0; i = this.entries[i].next)
{
if ((this.entries[i].hashCode == num) && this.comparer.Equals(this.entries[i].key, key))
{
if (num3 < 0)
{
this.buckets[index] = this.entries[i].next;
}
else
{
this.entries[num3].next = this.entries[i].next;
}
this.entries[i].hashCode = 1;
this.entries[i].next = this.freeList;
this.entries[i].key = default(TKey);
this.entries[i].value = default(TValue);
this.freeList = i;
this.freeCount++;
this.version++;
return true;
}
num3 = i;
}
}
return false;
}

SortedDictionary,MSDN 是这样描述的:

SortedDictionary<(Of <(TKey, TValue>)>) 泛型类是检索运算复杂度为 O(log n) 的二叉搜索树,其中 n 是字典中的元素数。就这一点而言,它与 SortedList<(Of <(TKey, TValue>)>)  泛型类相似。这两个类具有相似的对象模型,并且都具有 O(log n) 的检索运算复杂度。这两个类的区别在于内存的使用以及插入和移除元素的速度:

  1. SortedList<(Of <(TKey, TValue>)>)  使用的内存比 SortedDictionary<(Of <(TKey, TValue>)>) 少。
  2. SortedDictionary<(Of <(TKey, TValue>)>) 可对未排序的数据执行更快的插入和移除操作:它的时间复杂度为 O(log n),而 SortedList<(Of <(TKey, TValue>)>) 为 O(n)。
  3. 如果使 用排序数据一次性填充列表,则 SortedList<(Of <(TKey, TValue>)>) 比 SortedDictionary<(Of <(TKey, TValue>)>) 快。

SortedDictionary<K, V>是按照K有序排列的(K, V)数据结构,以红黑树作为内部数据结构对K进行排列保存– TreeSet<T>,红黑树是一棵二叉搜索树,每个结点具有黑色或者红色的属性。它比普通的二叉搜索树拥有更好的平衡性。2-3-4树是红 黑树在“理论”上的数据结构。

2-3-4树插入算法:类似于二叉搜索树的插入(插入数据插入到树的叶子结点) ,如果插入位置是2-结点或者3-结点,那么直接插入到当前结点,如果插入位置是4-结点,需要将当前的4-结点进行拆分,然后再执行后继的插入操作。

SortedDictionary Add

public void Add(T item)
{
if (this.root == null)
{
this.root = new Node<T>(item, false);
this.count = 1;
}
else
{
Node
<T> root = this.root;
Node
<T> node = null;
Node
<T> grandParent = null;
Node
<T> greatGrandParent = null;
int num = 0;
while (root != null)
{
num
= this.comparer.Compare(item, root.Item);
if (num == 0)
{
this.root.IsRed = false;
ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_AddingDuplicate);
}
if (TreeSet<T>.Is4Node(root))
{
TreeSet
<T>.Split4Node(root);
if (TreeSet<T>.IsRed(node))
{
this.InsertionBalance(root, ref node, grandParent, greatGrandParent);
}
}
greatGrandParent
= grandParent;
grandParent
= node;
node
= root;
root
= (num < 0) ? root.Left : root.Right;
}
Node
<T> current = new Node<T>(item);
if (num > 0)
{
node.Right
= current;
}
else
{
node.Left
= current;
}
if (node.IsRed)
{
this.InsertionBalance(current, ref node, grandParent, greatGrandParent);
}
this.root.IsRed = false;
this.count++;
this.version++;
}
}

我们来测试一下 Hashtable、Dictionary和SortedDictionary的插入和查找性能。

性能测试代码

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace DictionaryTest
{
class Program
{
private static int totalCount = 10000;

static void Main(string[] args)
{
HashtableTest();
DictionaryTest();
SortedDictionaryTest();
Console.ReadKey();
}

private static void HashtableTest()
{
Hashtable hastable
= new Hashtable();
Stopwatch watch
= new Stopwatch();
watch.Start();
for (int i = 1; i < totalCount; i++)
{
hastable.Add(i,
0);
}
watch.Stop();
Console.WriteLine(
string.Format(Hashtable添加{0}个元素耗时:{1}ms,totalCount, watch.ElapsedMilliseconds));
Console.WriteLine(
Hashtable不做查找测试);
hastable.Clear();
}

private static void DictionaryTest()
{
Dictionary
<int, int> dictionary = new Dictionary<int, int>();
Stopwatch watch
= new Stopwatch();
watch.Start();
for (int i = 1; i < totalCount; i++)
{
dictionary.Add(i,
0);
}
watch.Stop();
Console.WriteLine(
string.Format(Dictionary添加{0}个元素耗时:{1}ms,totalCount, watch.ElapsedMilliseconds));
watch.Reset();
watch.Start();
dictionary.Select(o
=> o.Key % 1000 == 0).ToList().ForEach(o => { });
watch.Stop();
Console.WriteLine(
string.Format(Dictionary查找能被1000整除的元素耗时:{0}ms, watch.ElapsedMilliseconds));
dictionary.Clear();
}

private static void SortedDictionaryTest()
{
SortedDictionary
<int, int> dictionary = new SortedDictionary<int, int>();
Stopwatch watch
= new Stopwatch();
watch.Start();
for (int i = 1; i < totalCount; i++)
{
dictionary.Add(i,
0);
}
watch.Stop();
Console.WriteLine(
string.Format(SortedDictionary添加{0}个元素耗时:{1}ms,totalCount, watch.ElapsedMilliseconds));
watch.Reset();
watch.Start();
dictionary.Select(o
=> o.Key % 1000 == 0).ToList().ForEach(o => { });
watch.Stop();
Console.WriteLine(
string.Format(SortedDictionary查找能被1000整除的元素耗时:{0}ms, watch.ElapsedMilliseconds));
dictionary.Clear();
}
}
}

最 终结果如图:

[转载]在线用户实体缓存解决方案

mikel阅读(1465)

[转载]在线用户实体缓存解决方案 – 木子博客 – 博客园.

随着网站访问量的增加,在线用户实体信息的存储方式变得重要起来。存储在线用户的信息一般有这三种方案:

1、用户的实体信息保存在Session里,简单方便,随着Session的过期用户信息自动过期。

2、用户信息保存在数据库中,用一个表存储在线的用户信息。

3、用户信息保存在内存。

当前项目用的是第一种方法,把用户的实体信息保存在Session中,虽然使用方便,但总感觉很别扭。Discuz!NT使用的是第二种方法,把在线用户 标识保存在一个表中,从cookie跟读取用户的ID,并从用户信息表查询该用户的信息,组装到实体中。如果有大量的用户在线同时操作时,这也不是一个很 好的解决办法。

这里选择第三种解决方案,把用户信息保存到内存。

我们使用Dictionary来存储用户信息,由于Dictionary不是线程安全的,因此需要注意只能单线程更新字典。

先定义一个保存用户信息的实体:

internal class UserEntity<T>
{
    /// <summary>
    /// 用户信息
    /// </summary>
    internal T UserInfo { get; set; }

    /// <summary>
    /// 添加到列表的时间戳
    /// </summary>
    internal DateTime Timestamp { get; set; }
}

使用泛型包装用户实体,并增加一个时间戳,表示该用户信息添加到内存的时间,过期是根据这个时间来判断的。

再增加一个在线用户信息管理类:

/// <summary>
/// 在线用户缓存管理
/// </summary>
/// <typeparam name="T"></typeparam>
public class UserCacheManager<T>
{
    #region 静态属性
    /// <summary>
    /// 静态用户缓存表
    /// </summary>
    private static Dictionary<long, UserEntity<T>> _UserList = new Dictionary<long, UserEntity<T>>();
    /// <summary>
    /// 过期时间
    /// </summary>
    private static int _ExpiredMinutes = 30;
    /// <summary>
    /// 定时器
    /// </summary>
    private static Timer _Timer = null;
    #endregion

    #region 静态构造函数
    /// <summary>
    /// 静态构造函数
    /// 初始化计时器
    /// </summary>
    static UserCacheManager()
    {
        _Timer = new Timer(new TimerCallback(TimerClear), null, 60000, _ExpiredMinutes * 60000);
    }
    #endregion

    #region 私有方法
    /// <summary>
    /// 清除在线用户
    /// </summary>
    /// <param name="sender"></param>
    private static void TimerClear(object sender)
    {
        ThreadPool.QueueUserWorkItem(new WaitCallback(AsyncClear));
    }

    /// <summary>
    /// 异步清除过期的在线用户
    /// </summary>
    /// <param name="sender"></param>
    private static void AsyncClear(object sender)
    {
        //当前时间
        DateTime timestamp = DateTime.Now.AddMinutes(0 - _ExpiredMinutes);
        //过期的用户列表
        var expiredUserList =
            (from userEntity in _UserList where userEntity.Value.Timestamp <= timestamp select userEntity);
        if (expiredUserList != null && expiredUserList.Count() > 0)
        {
            List<long> expiredUserIdentities = expiredUserList.Select(o => o.Key).ToList();
            lock (_UserList)
            {
                foreach (long userId in expiredUserIdentities)
                    _UserList.Remove(userId);
            }
        }
    }
    #endregion

    #region 公共方法
    /// <summary>
    /// 增加在线用户
    /// </summary>
    /// <param name="userIdentity">用户身份标识</param>
    /// <param name="userInfo">用户实体</param>
    public static void Add(long userIdentity, T userInfo)
    {
        lock (_UserList)
        {
            #region 创建用户实体
            UserEntity<T> userEntity = new UserEntity<T>
            {
                Timestamp = DateTime.Now,
                UserInfo = userInfo
            };
            #endregion

            if (_UserList.Keys.Contains(userIdentity))
            {
                _UserList[userIdentity] = userEntity;
            }
            else
            {
                _UserList.Add(userIdentity, userEntity);
            }
        }
    }

    /// <summary>
    /// 获取用户信息
    /// </summary>
    /// <param name="userIdentity"></param>
    /// <returns></returns>
    public static T Get(long userIdentity)
    {
        lock (_UserList)
        {
            if (_UserList.Keys.Contains(userIdentity))
            {
                _UserList[userIdentity].Timestamp = DateTime.Now;
                return _UserList[userIdentity].UserInfo;
            }
            else
            {
                return default(T);
            }
        }
    }

    /// <summary>
    /// 移除用户缓存信息
    /// </summary>
    /// <param name="userIdentity"></param>
    public static void Remove(long userIdentity)
    {
        if (_UserList.Keys.Contains(userIdentity))
        {
            lock (_UserList)
            {
                _UserList[userIdentity].Timestamp = DateTime.Now.AddDays(-1);
            }
        }
    }
    #endregion
}

Dictionary<long, UserEntity<T>> _UserList用来保存在线的用户列表。

在静态构造函数中声明了一个定时器,定时器负责清理过期的用户信息。并把清理用户信息的方法装入线程池执行。

MSDN:只要不修改Dictionary,Dictionary就可以同时支持多个阅读器。即便如此,从头到尾对一个集合进行枚举本质上并不是一个线程 安全的过程。当出现枚举与写访问互相争用这种极少发生的情况时,必须在整个枚举过程中锁定集合。若允许多个线程对集合执行读写操作,您必须实现自己的同 步。

所以在更新Dictionary中,都锁定了字典,防止多线程冲突。

源代码在经过富文本编辑器后显示有点问题,感兴趣的朋友可以  从这里下载源码

http://blog.moozi.net/archives/onlineusercachemanager/

[转载]C# 委托知识总结

mikel阅读(948)

[转载]C# 委托知识总结 – 情缘 – 博客园.

1.什么是委托,为什么要使用委托

我正在埋头苦写程序,突然想喝水,但是又不想自己去掉杯水而打断自己的思路,于是 我就想让女朋友去给我倒水。她去给我倒水,首先我得让她知道我想让她干什么,通知她之后我可以继续写自己的程序,而倒水的工作就交给了她。这样的过程就相 当于一个委托。

在 程序过程中,当程序正在处理某个事件的时候,我需要另外的程序代码去辅助处理一些事情,于是委托另一个程序模块去处理,而委托就可以达到这种目的,我可以 利用委托通知另外的程序模块,该去调用哪个函数方法。委托其实就起到了这样一个作用,将函数签名传递到了另一个函数中。或许这样讲还是有些模糊,看看后面 的具体实例。

2.委托的定义

delegate int Add(int num1,int num2);

delegate void
ConvertNum(string result);


上面是定义两个委托的例子,其实很简单。声明一个委托使用delegate关键字,上面分别是定义的带返回值的委托和不带返回值的委托,

两个委托都有传递参数,当然也可以不传递参数。
其实委托也是一个类,委托派生为System.MulticastDelegate,而System.MulticastDelegate

又继承System.Delegate,如果你
知道这个也就明白委托其实是一个特殊的类。

委托的简单实用例子

1 public delegate string TeaDelegate(string spText);
2
3 public class DelegateSource
4 {
5 public void TestDelegate()
6 {
7 Operator op = new Operator();
8 TeaDelegate tea = new TeaDelegate(op.GetTea);
9 Console.WriteLine(去给我倒杯水);
10 Console.WriteLine();
11 string result=tea(去给我倒杯水);
12 Thread.Sleep(5000);
13 Console.WriteLine(result);
14 Console.WriteLine();
15 }
16 }
17
18 public class Operator
19 {
20 /// <summary>
21 /// 确定是否还有水
22 /// </summary>
23 private bool flag = true;
24
25 public string GetTea(string spText)
26 {
27 if (spText == 去给我倒杯水)
28 {
29 if (flag)
30 {
31 return 老公,茶来了;
32 }
33 else
34 {
35 return 老公,没有水了;
36 }
37 }
38 return 等待…….;
39 }
40 }

输出结果


上面使用最普通的一种方式来定义了一个委托的使用,这个例子虽然很简单,但是能够很形象的描述委托的使用。

3. 委托的三种形式


(1).推断

推断委托例子

1 public delegate string TeaDelegate(string spText);
2
3 public class DelegateSource
4 {
5 public void TestDelegate()
6 {
7 Operator op = new Operator();
8 TeaDelegate tea = op.GetTea;
9 Console.WriteLine(去给我倒杯水);
10 Console.WriteLine();
11 string result=tea(去给我倒杯水);
12 Thread.Sleep(5000);
13 Console.WriteLine(result);
14 Console.WriteLine();
15 }
16 }
17
18 public class Operator
19 {
20 /// <summary>
21 /// 确定是否还有水
22 /// </summary>
23 private bool flag = true;
24
25 public string GetTea(string spText)
26 {
27 if (spText == 去给我倒杯水)
28 {
29 if (flag)
30 {
31 return 老公,茶来了;
32 }
33 else
34 {
35 return 老公,没有水了;
36 }
37 }
38 return 等待…….;
39 }
40 }

在委托定义的例子中我们看到委托的使用方法是在委 托实例化的时候指定的[new DelegateName(FunctionName)],这里可能表述不是太但是代码应该看得白了。 而委托的推断,并没有new 委托这个步骤,而是直接将Function 指定给委托。

(2).匿名函数

匿名委托例子

1 public delegate string TeaDelegate(string spText);
2
3 public class DelegateSource
4 {
5 public void TestDelegate()
6 {
7 Operator op = new Operator();
8 bool flag = true;
9 TeaDelegate tea = delegate(string spText)
10 {
11 if (spText == 去给我倒杯水)
12 {
13 if (flag)
14 {
15 return 老公,茶来了;
16 }
17 else
18 {
19 return 老公,没有水了;
20 }
21 }
22 return 等待…….;
23 };
24
25 Console.WriteLine(去给我倒杯水);
26 Console.WriteLine();
27 string result=tea(去给我倒杯水);
28 Thread.Sleep(5000);
29 Console.WriteLine(result);
30 Console.WriteLine();
31 }
32 }


至于匿名委托,给人的感觉更为直接了,都不用显示的指定方法名,因为根本没有方法,而是指定的匿名方法。匿名方法在.NET 中提高了

代码的可读性和优雅性。对于更多操作较少的方法
直接写为匿名函数,这样会大大提高代码的可读性。这里有两个值得注意的地方: 第一,不能使用

跳转语句跳转到该匿名方法外,第二
不能使用ref,out修饰的参数

(3).多播委托

多播委托例子

1 public delegate string TeaDelegate(string spText);
2
3 public class DelegateSource
4 {
5 public void TestDelegate()
6 {
7 Operator op = new Operator();
8
9 TeaDelegate tea1 = op.GetTea;
10 TeaDelegate tea2 = op.Speak;
11 TeaDelegate tea = tea1 + tea2;
12
13 Console.WriteLine(去给我倒杯水);
14 Console.WriteLine();
15 string result=tea(去给我倒杯水);
16 Thread.Sleep(5000);
17 Console.WriteLine(result);
18 Console.WriteLine();
19 }
20 }
21
22 public class Operator
23 {
24 /// <summary>
25 /// 确定是否还有水
26 /// </summary>
27 private bool flag = true;
28
29 public string GetTea(string spText)
30 {
31 if (spText == 去给我倒杯水)
32 {
33 if (flag)
34 {
35 return 老公,茶来了;
36 }
37 else
38 {
39 return 老公,没有水了;
40 }
41 }
42 return 等待…….;
43 }
44
45
46 public string Speak(string spText)
47 {
48 Console.WriteLine(\n去把我的设计图稿拿来);
49 return null;
50 }
51 }


还是上面的那个实例,我不尽想让女朋友去给我掉杯水,还让她帮我将程序设计图稿拿过来。这个时候做的就不是一件事了,而是多件。

程序中也有很多这种情况,于是我们需要多播委
托,在一个委托上指定多个执行方法,这是在程序中可以行的。上面提到了,委托直接继承于

System.MulticastDelegate,
正是因为这个类可以实现多播委托。
如果调用多播委托,就可以按顺序连续调用多个方法。为此,委托的签名就必须返回void;否则,就只能得 到委托调用的最后一个方法的结果。所以在上面的这段代码中是得不到结果的

4.事件

使用C#编程,无论是 WinForm,WebForm 给人很难忘得就是它的控件,而他们的控件库使用方式都是使用使用事件驱动模式,而事件驱动模式却少不了委托。话不多说,看代码能够更清好的理解事件和委托 之间的联系.

事件的使用

1 public delegate void MyDelegate(string name);
2
3 public class EventSource
4 {
5 public event MyDelegate Event_Delegate;
6
7 public void SetCustomer(string name)
8 {
9 Console.WriteLine(事件发 生…..\n);
10 Console.WriteLine(hi! +name);
11 }
12
13 public void TestEvent()
14 {
15 EventSource source = new EventSource();
16 Console.WriteLine(订阅事件…..\n);
17 source.Event_Delegate += new MyDelegate(source.SetCustomer);
18 Console.WriteLine(触发事 件…..\n);
19 source.Event_Delegate(hechen);
20 Console.WriteLine(………………);
21 }
22 }


上面的代码中我们定义了一个委托,然后定义了一个类EventSource,这个类中声明了一个事件。定义一个事件使用event 关键字,定义一

个event必须指定这个event传递消息的委托,在触发事件之前必需订阅事件,我们使用+=
new 语法来订阅一个事件,也就相当于实例化一个事件。

当我们触发事件的时候,就会调用相应的方法去处理。

5. 泛型委托


委托是类型安全的引用,泛型委托就和我们常用的泛型类一样,这个类在使用的时候才能确定类型.通过泛型委托,我们可以在委托传递参数

之后知道它的类型.在.NET中有一个很典型的
泛型委托:

public delegate voie EventHandler<TEventArgs>(object
sender,TEventArgs e) where TEventArgs:EventArgs.

这是一个非常有特色的泛型委托,可能我们用的比较
少,但是作用是不能忽视的。
我们看看三个非常具有代表性的泛型委托.现在.NET4.0已经出来了,但是泛型委托.NET2.0就出来了,Linq 大家用的那叫一个甜,

为啥
函数式编程风格,匿名方法,Lamda表达式表达式使用是如此的魅力。但是大家仔细观察过没有,Linq 中的方法有几个经常出现的参数:

Action<T>,Predicate<T>,Func<T,
Result>

Func<T, E>:封装一个具有一个参数并返回 E 参数指定的类型值的方法,T 是这个委托封装方法的参数类型,E是方法的返回值类型。当然Func<T, Result> 只是其中的一种情况,这个委托还有其他的几种情况:Func<T> 这个是方法没有参数,返回值类型是T;Func<T1,T2,Result> 这个方法有两个参数,类型分别为T1,T2,返回值是Result,还有 Func<T1,T2,T3,Result>,Func<T1,T2,T3,T4,Result> 这几中情况,具体情况就不介绍了.我们还可以通过扩展类型,扩展为更多的参数.

Func 委托的使用

1 public void TestFunc()
2 {
3 TEventSource eventSource=new TEventSource();
4 Func<string, string> func = eventSource.GetTea;
5 string result = func();
6 Console.WriteLine(result);
7 }
8
9 public string GetTea(string context)
10 {
11 if (context == )
12 {
13 return 茶来了;
14 }
15 else
16 {
17 return 设计稿子来了;
18 }
19 }


Action<T>:封装一个方法,该方法只采用一个参数并且不返回值,包括
Action<T>,Action<T1,T2>,Action<T1,T2,T3>,Action<T1,T2,T3,T4>
几种情况,也可以
通过扩展方法去扩展参数的个数

Action 委托使用例子

1 public void TestAction()
2 {
3 TEventSource eventSource = new TEventSource();
4 Action<string> action = eventSource.Speak;
5 action(Action<T> 泛型委托);
6 }
7
8 public void Speak(string context)
9 {
10 Console.WriteLine(context);
11 }


Predicate<T>:表示定义一组条件并确定指定对象是否符合这些条件的方法。该委托返回的是一个bool类型的值,如果比较满足条件

返回true,否则返回false.其实上面的
Func 委托可以包含这个委托.不过这个委托和上面的两个不一样,它只有一种类型

Predicate 委托使用例子

1 public void TestPredicate()
2 {
3 TEventSource eventSource = new TEventSource();
4 Predicate<int> predicate = eventSource.IsRigth;
5 Console.WriteLine(predicate(0));
6 }
7
8 public bool IsRigth(int value)
9 {
10 if (value == 0)
11 {
12 return true;
13 }
14 else
15 {
16 return false;
17 }
18 }

6. 异步委托

投 票技术: 委托其实相当于一个线程,使用投票技术是使用异步委托的一种实现方式.Delegate类提供了方法BeginInvoke(),可以传送委托类型定义的 输入参数,其返回类型为IAsyncResult。IAsyncResult的IsCompleted属性可以判断委托任务是否完成

异步委托投票技术

1 public delegate int DelegateVote(int data, int ms);
2 /// <summary>
3 /// 使用投票操作完成委托任务
4 /// </summary>
5 public class VoteDelegate
6 {
7 /// <summary>
8 /// 休眠特定时间执行操作
9 /// </summary>
10 /// <param name=”data”></param>
11 /// <param name=”ms”></param>
12 /// <returns></returns>
13 public static int TakeWork(int data, int ms)
14 {
15 Console.WriteLine(开始调用TakeWork方法);
16 Thread.Sleep(ms);
17 Console.WriteLine(结束调用 TakeWork方法);
18 return data + 10;
19 }
20
21 public void TestDelegate()
22 {
23 DelegateVote voteDel = TakeWork;
24 IAsyncResult result = voteDel.BeginInvoke(1,5000,null,null);
25 while (result.IsCompleted == false)
26 {
27 Console.WriteLine(等待……);
28 Thread.Sleep(500);
29 }
30 int value = voteDel.EndInvoke(result);
31 Console.WriteLine(委托调用结果: +value);
32 }
33 }

等待句柄:等待句柄是使用 AsyncWaitHandle属性访问,返回一个WaitHandle类型的对象,它可以等待委托线程完成其任务。在这个参数中可以设置最大的等待时 间。

异步委托等待句柄

1 public delegate string WaitDelegate(string content);
2
3 public class WaitHandlerDelegate
4 {
5 public void TestWaitHander()
6 {
7 WaitDelegate del = GetTea;
8 IAsyncResult ar = del.BeginInvoke(hechen, null, null);
9 while (true)
10 {
11 Console.Write(.);
12 if (ar.AsyncWaitHandle.WaitOne(50, false))
13 {
14 break;
15 }
16 }
17 string result=del.EndInvoke(ar);
18 Console.WriteLine(result);
19
20 }
21
22 public static string GetTea(string content)
23 {
24 return 茶来了 +content;
25 }
26 }

异步回调:这个方式和投票技术 有点类似,不过在投票方式中BeginInvoke()方法第三个参数指定了一个方法签名,而这个方法参数接收IAsyncResult 类型的参数。

异步委托回调函数

1 public delegate string AsyDelegate(string content);
2
3 public class AsyncresultDelegate
4 {
5 public void TestAsync()
6 {
7 AsyDelegate del = GetTea;
8 del.BeginInvoke(hechen, delegate(IAsyncResult ar) {
9 Thread.Sleep(5000);
10 string result = del.EndInvoke(ar);
11 Console.WriteLine(result);
12 }, null);
13 for (int i = 0; i < 100; i++)
14 {
15 Console.WriteLine(等待…..);
16 Thread.Sleep(1000);
17 }
18 }
19
20 public static string GetTea(string content)
21 {
22 return 茶来了 + content;
23 }
24 }

[转载]Asp.Net 网站性能优化 缓字决 (上) 缓冲写数据

mikel阅读(1108)

[转载]Asp.Net 网站性能优化 缓字决 (上) 缓冲写数据 – 欢迎光临赵玉开的技术博客 – 博客园.

通常情况下ASP.NET 网站的底层数据存储都是关系数据库,关系数据库资源比较昂贵,而且也很容易造成瓶颈。缓字决文章就是为大家介绍如何有效使用缓存,异步写缓冲数据库的压 力,从而保证网站的性能。

大家已经看到很多关于ASP.NET缓存的文章了。所以我写的时候要改变一下思路,从缓冲写数据开始说起。缓冲写数据的意思是在数据需要更新时不马 上把数据存到数据库,而是先缓冲一下,然后在适当的时机再写入到数据库中。

缓冲写数据可以避免在网站并发访问多的时候,数据库瞬间承受过大压力,而造成死锁或响应不及时的情况。

那么什么时候适合缓冲写呢?是不是所有情况都适用呢?缓冲写会导致数据在内存中或者web server硬盘或者第三方存储中驻留一段时间,在这段时间内如果从数据库中查询最新数据的话,会有遗漏。大多数事物都有两面性,我们需要学会趋利避害; 换句话说在保证缓冲写不会导致用户感觉数据缺少的情况下,或者在使用适当措施不让用户感觉数据缺失的情况下就可以使用缓冲写。我有两个具体的实例来介绍如 何使用缓冲写:

1. Pv(页面浏览量)统计,大多数网站都有这个功能,有些网站还专门做这个服务
网站每有一个页面被浏览时,就会需要给对应页面的 Pv+1;这种情况下,如果直接更新到数据库中,访问量稍微大一些就会造成数据库压力过大的问题。

所以我们需要对Pv计数做缓冲,在单web server的情况下我们可以在内存中维护一个hashtable,然后用一个异步的线程去定时扫描这个hashtable,当点击数达到“一定数字”时 更新到数据库。听上去很简单,不过也需要一个小技巧,上一句话中说的“一定数字”四个字不能是随便的一个数字,如果它是4,试想一下会出现什么情况,我们 的所有Pv数都会是4的倍数,用户会怀疑我们是不是在Pv上造假了;我们没有造假却留下了造假的迹象!这个“一定数字”必须是一个素数,我们可以取7,也 可以用13,如果我们的访问量很大也可以取23或31;这样就不会出现“造假的迹象”了。

2. 发送站内短消息
站内短消息也是一个比较通用的模块,他可以说是一种离线消息,并非im即时消息,所以我们可以利用这个业务特性,   来对发消息做下缓冲。当有短消息发送时我们可以先将这个短消息放到硬盘文件中,然后很快的响应用户,在ui上告诉用户,你的消息已经发出去了,然后我 们可以用另一个线程(或者做一个windows服务)去监视缓冲短消息的目录顺序的将短消息存储到数据库中。

以上两个场景都是比较经典的利用缓冲写的例子,在现实中需要我们去具体分析业务和某个业务是否会造成数据库压力来决定是否缓冲写,如果业务本身对数 据库的压力就很小,那当然就没必要考虑了,反之如果业务压力较大我们就需要做一些工作避免缓冲写的问题并利用缓冲写。


请尊重作者的劳动,转载请保留链接 玉开的技术博客

[转载]Asp.Net 网站优化 数据库优化措施 使用主从库(下) - 欢迎光临赵玉开的技术博客 - 博客园

mikel阅读(1037)

[转载]Asp.Net 网站优化 数据库优化措施 使用主从库(下) – 欢迎光临赵玉开的技术博客 – 博客园.

上 一篇中我们配置好了主从库,现在我们尝试在程序中使用主从库。

主从库之间是一种发布订阅的关系,发布者和订阅者之间并非实时同步的,通常会有几分钟的延时,更有甚者会有几个小时的延时。所以我们需要通过合理的 使用来避开有延时这个问题。

我们希望主库尽可能的少参与查询,来提高写的及时性;同时要让从库在不影响读出数据的准确及时的前提下尽可能的分担主库的压力。

主从两个库需要在配置文件中配置两个连接字符串,CONN_Master和CONN_Slave。我们需要设定一些规则决定当前的查询应该从主库查 还是需要从从库查。这个规则没有定式,只能根据业务需要来确定。下面我举几个例子来说明:

1. 以豆瓣读书书的详细页为假定场景,你可以点击这里看下页面的结构(我不是豆瓣的技术,在这里只是拿这个页面举例)
我们来分析呈现这个 页面需要的数据和这些数据的实效性要求
1) 书的详细信息 时效性要求:要求及时
2) 豆瓣成员的常用标签 实效性:不需要很及时
3) 喜欢读这本书的人也喜欢读的书 属于分析数据,不需要很及时
4) 最新书评 要求及时
5) 读这本书的几个用户 及时性不高
6) 喜欢这本书的人常去的小组 属于分析数据不需要很及时
从上面的分析可以看出只有1),4)两项数据需要从主库读,而2),3),5),6)为非 及时数据从从库读取即可。当然我们可以对这些实效性不高的数据做缓存处理。

2. 以论坛帖子列表页面为假定场景,玩论坛的人都喜欢顶贴,把自己的帖子顶到第一页让更多的人关注,而对于50页之后的帖子则反读的人很少;我们可以根据这个 业务逻辑特征来决定在用户访问前50页帖子列表数据时从主库读,而当用户访问超过50页之后的数据时则从从库进行查询。

3. 以订单为例,通常超过三个月的订单就不会再有变化了,假定我们把订单号设计为日期格式时,根据订单号去查询订单时就可以根据订单号来决定该访问主库还是从 库。

举了几个适用的场景,我们以第三个场景为例,写一段简单的示意代码看下

01 //orderNo 的格式为 20100528120105000001 即yyyyMMddHHmmss + 序号
02 public OrderInfo GetOrder(string orderNo) {
03 string connString = ConnStringGetter.GetForOrder(orderNo);
04 using (SQLConnection conn = new SQLConnection(connString))
05 {
06 ...
07 }
08 }
09
10 public class ConnStringGetter
11 {
12 public static string GetForOrder(string orderNo) {
13 int year = int.Parse(orderNo.Substring(0,4));
14 int money = int.Parse(orderNo.Substring(4,2));
15 int date = int.Parse(orderNo.Substring(6,2));
16 DateTime orderTime = new DateTime(year, money, date);
17
18 TimeSpan ts = DateTime.Now - orderTime;
19 //根据订单的时间决定使用主库还是从库
20 if (ts.TotalDays > 30) return ConfigurationManager.ConnectionStrings["CONN_Slave"].ConnectionString;
21 return ConfigurationManager.ConnectionStrings["CONN_Master"].ConnectionString;
22 }
23 }

正确的使用主从库,可以很好的提升系统的性能。使用主库还是从库的选择权决定在业务逻辑的手里。

相关随笔:Asp.Net 网站优化 数据库优化措施 使用主从库(上)


请尊重作者的劳动,转载请保留链接 玉开的技术博客

[转载]Asp.Net 网站优化 数据库优化措施 使用主从库(上)

mikel阅读(1015)

[转载]Asp.Net 网站优化 数据库优化措施 使用主从库(上) – 欢迎光临赵玉开的技术博客 – 博客园.

网站规模到了一定程度之后,该分的也分了,该优化的也做了优化,但是还是不能满足业务上对性能的要求;这时候我们可以考虑使用主从库。

主从库是两台服务器上的两个数据库,主库以最快的速度做增删改操作+最新数据的查询操作;从库负责查询较旧数据,做一些对实效性要求较小的分析,报 表生成的工作。这样做将数据库的压力分担到两台服务器上从而保证整个系统响应的及时性。

SQL Server提供了复制机制来帮我们实现主从库的机制。我们看下如何在SQL server 2005中实践:

实践前需要新创建一个Test的数据库,这个库中建一个测试表。

1. 打开sql server企业管理器,在对象资源管理器里面选择复制本地发布,右键选择新建发布

2. 打开新建发布向导,点下一步,选择发布数据的数据库

3. 我们选择Test数据库,并点击下一步,选择发布类型

这 里我们选择的是事务性发布,事务性发布保证数据在做更新之后尽可能快的分发到订阅服务器上。有关其他几种发布类型的使用场景请参考msdn
4. 点击下一步,选择要发布的对象,这里我们只对表进行发布

5. 点击下一步进入筛选数据设置,这里我们要复制表的所有数据所以不做设置

6. 点击下一步,指定何时运行快照,我们选择初始话数据,并选择默认的运行快照频率

7. 继续下一步,设置快照代理的运行账户,我们选择sql server agent账户

8. 点击下一步选择创建发布,再次点击下一步设置发布的名称

9. 点击完成,完成发布的设置,并创建发布,现在在本地发布出新添加了我们创建的发布

现在成功创建了发布,我们还需要创建订阅:在本地订阅文件夹上右击新建订阅,通过向导可以很容易的创建订阅,创建订阅时可以选择以发布者推送或者订 阅者主动的方式创建。具体步骤如下:
1. 通过右键菜单打开新建订阅,点击下一步,选择我们刚刚创建的发布作为订阅源


2. 选择是以推送还是以主动请求的方式同步数据,我们选择主动订阅

3. 设置执行分发代理的账户

4. 设置代理请求同步的频率

5. 设定是否立即做数据的初始化操作

6. 完成创建订阅

创建完成之后,我们可以通过在主库表中插入n条数据,然后在从库中查询的方式验证复制是否成功。

在Sql server2005中的复制创建起来很简单,我们需要根据业务需要设定复制的类型和同步的频率,下一篇我们谈谈如何有效的在程序中使用主从库。


请尊重作者的劳动,转载请保留链接 玉开的技术博客

[转载]ASP.NET 4新增功能(一) ASP.NET核心服务

mikel阅读(906)

[转载]ASP.NET 4新增功能(一) ASP.NET核心服务 – longgel – 博客园.

1.可扩展输出缓存

自从ASP.NET 1.0发布开始,一般都是通过使用输出缓存将页、控件和http响应保存在内存中。对于后续的Web请求,ASP.NET可以从内存中检索存在的缓存输出 并不是从头开始重新生成输出,从而更快地提供响应。但该方法有一个限制,就是缓存的数据必须存储在内存中。在负载较大的服务器上,输出缓存的内存需求可能 会和Web应用程序其它它部分的内存需求产生冲突。

ASP.NET 4 为输出缓存增加了扩展性,使您能够配置一个或多个自定义输出缓存提供程序。输出缓存提供程序可使用任何存储机制保存 HTML 内容。这些存储选项包括本地或远程磁盘、云存储和分布式缓存引擎。

借助ASP.NET 4 中可以定制输出缓存提供程序的功能,我们就可以为网站设计更为主动而且更加智勇双全能的输出缓存策略。例如,我们可以创建这样一个输出缓存提供程序,该程 序将站点流量“排名前10”的页面缓存在内存里,而将其它的页面缓存在磁盘里。或者,也可以对所呈现页面的各种变化因素组合进行缓存,但应该使用分布式缓 存以减少前端Web服务器的内存消耗。

我们可以创建继承自OutPutCacheProvider类型的类来自定义输出缓存提供程序。随后,可以通过在web.config中进行配置该提供程 序,设置outputCache节点的providers子节点,如下面的示例所示:

<caching>
<outputCache defaultProvider=”AspNetInternalProvider”>
<providers>
<add name=”DiskCache”
type
=”Test.OutputCacheEx.DiskOutputCacheProvider, DiskCacheProvider”/>
</providers>
</outputCache>
</caching>

在ASP.NET 4 的默认输出缓存策略中。所有的HTTP响应、所呈现的页面和控件缓存均使用上例所示的默认输出缓存提供程序(其中defaultProvider属性值为 AspNetInternalProvider)。通过为defaultProvider指定不同的提供程序。就可以更改web应用程序的默认输出缓存提 供程序。

另外,还可以针对每个用户控件和各个请求选择不同的输出缓存提供程序。要为不同的Web用户控件选择不同的输出缓存提供程序,最简便的方法 是设置页面或控件指令中新增加的providerName属性,如下面的示例所示:

<%@ OutputCache Duration="60" VaryByParam="None" 
    providerName="DiskCache" %>

若要为某个HTTP请求指定不同的输出缓存提供程序,可以覆盖Global.asax文件中新增加的 GetOutputCacheProviderName方法,以编程的方式指定要用于特定请求的提供程序。

2.预加载Web应用程序

某些Web应用程序在第一次请求提供服务之前,需要加载大量的数据或执行开销很大的初始化处理。在ASP.NET早期版本中,对于此类情况,必须采用自定 义方法“唤醒”ASP.NET应用程序,然后在Global.asax文件中的Application_Load方法中运行初始化代码。

为应对这种情况,当ASP.NET 4在Windows Server 2008 R2上的IIS7.5中运行时,ASP.NET 4提供一种新的应用程序预加载管理器。预加载功能提供了一种可控的方法,用于启动应用程序池,初始化ASP.NET应用程序,然后接受HTTP请求。通过 这种方法,您可以在处理第一项HTTP请求之前执行开销很大的应用程序初始化的工作。例如,可以使用预加载管理器初始化某个应用程序,然后向负载平衡器发 生信号,告知应用程序已初始化并做好接受HTTP请求的准备。

若要使用应用程序预加载管理器,就需要配置applicationHost.config文件,设置IIS 7.5中的应用程序池为自动启动,配置如下:

<applicationPools>
<add name=”MyApplicationPool” startMode=”AlwaysRunning” />
</applicationPools>

由 于一个应用程序池可以包含多个应用程序,因此我们就需要通过使用applicationHost.config文件中的以下配置分别指定要自动启动的各个 应用程序:

<sites>
<site name=”MySite” id=”1″>
<application path=”/”
serviceAutoStartEnabled
=”true”
serviceAutoStartProvider
=”PrewarmMyCache” >
<!– Additional content –>
</application>
</site>
</sites>

<!– Additional content –>

<serviceAutoStartProviders>
<add name=”PrewarmMyCache”
type
=”MyNamespace.CustomInitialization, MyLibrary” />
</serviceAutoStartProviders>

3.永久重定向页面

在应用程序的生命周期内,Web应用程序中有可能修改url的显示规则。

在ASP.NET 中,开发人员处理对旧的URL的请求的传统方式是使用Redirect方法将请求转发至新的URL。然而,Redirect方法会发出HTTP 302临时重定向。这会产生额外的HTTP往返。也不是对搜索引擎的友好。

ASP.NET 4 增加了一个RedirectPermanent帮助方法,使用该方法可以方便地发生http 301(永久跳转)的响应,如下面的示例所示:

RedirectPermanent("/newpath/foroldcontent.aspx");
4.会话状态压缩

默认情况下,ASP.NET 提供两用于存储整个Web应用程序中的会话状态的选项。第一个选项是一个调用进程外会话状态服务器的会话状态提供程序。第二个选项是一个在 Microsoft SQL Server数据库中存储数据的会话状态提供程序。

由于这两个选项均在 Web 应用程序的工作进程之外存储状态信息,因此在将会话状态发送至远程存储器之前,必须对其进行序列化。如果会话状态中保存了大量数据,序列化数据的大小可能 变得很大。

ASP.NET 4 针对这两种类型的进程外会话状态提供程序引入了一个新的压缩选项。使用此选项,在 Web 服务器上有多余 CPU 周期的应用程序可以大大缩减序列化会话状态数据的大小。

可以使用配置文件中 sessionState 元素的新增加的 compressionEnabled 属性设置此选项。当 compressionEnabled 配置选项设置为 true 时,ASP.NET 使用 .NET Framework GZipStream类对序列化会话状态进行压缩和解压缩。下面的示例演示如何设置该特性。

<sessionState
mode=”SQLServer
SQLConnectionString
=”data source=dbserver;Initial Catalog=aspnetstate”
allowCustomSqlDatabase
=”true”
compressionEnabled
=”true”
/>