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

當(dāng)一個(gè)程序集被加載使用的時(shí)候,出于數(shù)據(jù)的完整性和安全性考慮,程序集文件(在99.9998%的情況下是.dll文件)會(huì)被鎖定,如果此時(shí)你想更新程序集(實(shí)際上是替換dll文件),是不可以操作的,這時(shí)你得把應(yīng)用程序退出,替換文件后再啟動(dòng)程序。

多數(shù)情況下這樣做是可行的,只是有時(shí)候,比如ASP.NET或一些需要一直運(yùn)行的服務(wù)進(jìn)程,重啟程序來更新好像不太好。

要是想對(duì)程序集進(jìn)行熱更新,即在程序運(yùn)行的同時(shí)替換文件,有一個(gè)大家很熟悉的方案——影像復(fù)制,如果你不熟悉.net,你肯定沒聽說過的。當(dāng)然了,這個(gè)叫法也挺難聽的,沒辦法,只好這樣翻譯,原詞是 Shadow Copy ,Shadow是影子,陰影,影像的意思,那也只好這么翻譯了。不過,你不用擔(dān)心它很抽象很高端,其實(shí),只要用心學(xué),沒什么東西是攻不克的。

我用一句話來概括一下影子復(fù)制(也可以叫拷貝,但我不喜歡拷貝這個(gè)詞,很黃很暴力的感覺)——應(yīng)用程序域在加載程序集時(shí),會(huì)把程序集文件復(fù)制到另一個(gè)地方,再進(jìn)行加載。這樣一來,當(dāng)程序集文件被使用時(shí),它鎖定的是復(fù)制后的文件,即原始文件我們可以放心地去替換了,等到合適的時(shí)間,把應(yīng)用程序重新啟動(dòng)一下,再次運(yùn)行時(shí),就會(huì)自動(dòng)把最新的程序集復(fù)制到緩存的目錄下,然后執(zhí)行最新版本的代碼。最好把這些代碼的調(diào)用放到一個(gè)新的應(yīng)用程序域中執(zhí)行,因?yàn)檫@樣的好處是不用重新啟動(dòng)應(yīng)用程序,而只要把某個(gè)應(yīng)用程序域卸載掉再重新創(chuàng)建一個(gè)新的,就會(huì)自動(dòng)加載最新的程序集了。而且,通常你都應(yīng)該這么做的,創(chuàng)建一個(gè)應(yīng)用程序域,在里面執(zhí)行代碼,執(zhí)行完了就把應(yīng)用程序域卸掉,可以節(jié)約資源。

應(yīng)用程序在運(yùn)行的時(shí)候,默認(rèn)會(huì)創(chuàng)建一個(gè)應(yīng)用程序域的,說白了,一個(gè)進(jìn)程中至少會(huì)有一個(gè)應(yīng)用程序域,如果你把某段代碼放到一個(gè)新的應(yīng)用程序域中執(zhí)行,并且你希望執(zhí)行完后,可以把結(jié)果傳回給主應(yīng)用程序域,那就用老周以前寫過的方法,記得老周前面寫過的,想按引用傳遞對(duì)象,就從MarshalByRefObject類派生,想讓對(duì)象按值傳遞,就讓它支持序列化。

 

在創(chuàng)建新的應(yīng)用程序域時(shí),可以同時(shí)傳遞一個(gè)SetupInfo對(duì)象,這個(gè)對(duì)象有一個(gè) ShadowCopyFiles 屬性,雖然它定義的類型是 string,但你千萬不要理解錯(cuò),不要把一個(gè)文件的路徑賦給它。老周以前就見到一位朋友理解錯(cuò)了,它誤以為這個(gè)屬性是用來設(shè)置復(fù)制程序集文件的緩存路徑,結(jié)果代碼寫了老是不行。唉,這就是不看MSDN的下場(chǎng)。

不要亂來,設(shè)置復(fù)制程序集的緩存目錄是 CachePath 屬性,不是 ShadowCopyFiles 屬性。ShadowCopyFiles 屬性只能用兩個(gè)字符串的值,如果要啟用影像復(fù)制,就設(shè)置為 true,如果想禁用,就設(shè)置 false 或者干脆保持默認(rèn)的null值。也就說,它是一個(gè)用字符串表示的 bool 值。

 

下面,我們用一個(gè)例子來表演一下,很精彩的。

首先,弄一個(gè)類庫(kù)項(xiàng)目,然后在里面寫一個(gè)全宇宙最簡(jiǎn)單的類。