【.net 深呼吸】程序集的热更新

当一个程序集被加载使用的时候,出于数据的完整性和安全性考虑,程序集文件(在99.9998%的情况下是.dll文件)会被锁定,如果此时你想更新程序集(实际上是替换dll文件),是不可以操作的,这时你得把应用程序退出,替换文件后再启动程序。

多数情况下这样做是可行的,只是有时候,比如ASP.NET或一些需要一直运行的服务进程,重启程序来更新好像不太好。

要是想对程序集进行热更新,即在程序运行的同时替换文件,有一个大家很熟悉的方案——影像复制,如果你不熟悉.net,你肯定没听说过的。当然了,这个叫法也挺难听的,没办法,只好这样翻译,原词是 Shadow Copy ,Shadow是影子,阴影,影像的意思,那也只好这么翻译了。不过,你不用担心它很抽象很高端,其实,只要用心学,没什么东西是攻不克的。

我用一句话来概括一下影子复制(也可以叫拷贝,但我不喜欢拷贝这个词,很黄很暴力的感觉)——应用程序域在加载程序集时,会把程序集文件复制到另一个地方,再进行加载。这样一来,当程序集文件被使用时,它锁定的是复制后的文件,即原始文件我们可以放心地去替换了,等到合适的时间,把应用程序重新启动一下,再次运行时,就会自动把最新的程序集复制到缓存的目录下,然后执行最新版本的代码。最好把这些代码的调用放到一个新的应用程序域中执行,因为这样的好处是不用重新启动应用程序,而只要把某个应用程序域卸载掉再重新创建一个新的,就会自动加载最新的程序集了。而且,通常你都应该这么做的,创建一个应用程序域,在里面执行代码,执行完了就把应用程序域卸掉,可以节约资源。

应用程序在运行的时候,默认会创建一个应用程序域的,说白了,一个进程中至少会有一个应用程序域,如果你把某段代码放到一个新的应用程序域中执行,并且你希望执行完后,可以把结果传回给主应用程序域,那就用老周以前写过的方法,记得老周前面写过的,想按引用传递对象,就从MarshalByRefObject类派生,想让对象按值传递,就让它支持序列化。

 

在创建新的应用程序域时,可以同时传递一个SetupInfo对象,这个对象有一个 ShadowCopyFiles 属性,虽然它定义的类型是 string,但你千万不要理解错,不要把一个文件的路径赋给它。老周以前就见到一位朋友理解错了,它误以为这个属性是用来设置复制程序集文件的缓存路径,结果代码写了老是不行。唉,这就是不看MSDN的下场。

不要乱来,设置复制程序集的缓存目录是 CachePath 属性,不是 ShadowCopyFiles 属性。ShadowCopyFiles 属性只能用两个字符串的值,如果要启用影像复制,就设置为 true,如果想禁用,就设置 false 或者干脆保持默认的null值。也就说,它是一个用字符串表示的 bool 值。

 

下面,我们用一个例子来表演一下,很精彩的。

首先,弄一个类库项目,然后在里面写一个全宇宙最简单的类。