【.net 深呼吸】程序集的熱更新
當(dāng)一個程序集被加載使用的時候,出于數(shù)據(jù)的完整性和安全性考慮,程序集文件(在99.9998%的情況下是.dll文件)會被鎖定,如果此時你想更新程序集(實際上是替換dll文件),是不可以操作的,這時你得把應(yīng)用程序退出,替換文件后再啟動程序。
多數(shù)情況下這樣做是可行的,只是有時候,比如ASP.NET或一些需要一直運(yùn)行的服務(wù)進(jìn)程,重啟程序來更新好像不太好。
要是想對程序集進(jìn)行熱更新,即在程序運(yùn)行的同時替換文件,有一個大家很熟悉的方案——影像復(fù)制,如果你不熟悉.net,你肯定沒聽說過的。當(dāng)然了,這個叫法也挺難聽的,沒辦法,只好這樣翻譯,原詞是 Shadow Copy ,Shadow是影子,陰影,影像的意思,那也只好這么翻譯了。不過,你不用擔(dān)心它很抽象很高端,其實,只要用心學(xué),沒什么東西是攻不克的。
我用一句話來概括一下影子復(fù)制(也可以叫拷貝,但我不喜歡拷貝這個詞,很黃很暴力的感覺)——應(yīng)用程序域在加載程序集時,會把程序集文件復(fù)制到另一個地方,再進(jìn)行加載。這樣一來,當(dāng)程序集文件被使用時,它鎖定的是復(fù)制后的文件,即原始文件我們可以放心地去替換了,等到合適的時間,把應(yīng)用程序重新啟動一下,再次運(yùn)行時,就會自動把最新的程序集復(fù)制到緩存的目錄下,然后執(zhí)行最新版本的代碼。最好把這些代碼的調(diào)用放到一個新的應(yīng)用程序域中執(zhí)行,因為這樣的好處是不用重新啟動應(yīng)用程序,而只要把某個應(yīng)用程序域卸載掉再重新創(chuàng)建一個新的,就會自動加載最新的程序集了。而且,通常你都應(yīng)該這么做的,創(chuàng)建一個應(yīng)用程序域,在里面執(zhí)行代碼,執(zhí)行完了就把應(yīng)用程序域卸掉,可以節(jié)約資源。
應(yīng)用程序在運(yùn)行的時候,默認(rèn)會創(chuàng)建一個應(yīng)用程序域的,說白了,一個進(jìn)程中至少會有一個應(yīng)用程序域,如果你把某段代碼放到一個新的應(yīng)用程序域中執(zhí)行,并且你希望執(zhí)行完后,可以把結(jié)果傳回給主應(yīng)用程序域,那就用老周以前寫過的方法,記得老周前面寫過的,想按引用傳遞對象,就從MarshalByRefObject類派生,想讓對象按值傳遞,就讓它支持序列化。
在創(chuàng)建新的應(yīng)用程序域時,可以同時傳遞一個SetupInfo對象,這個對象有一個 ShadowCopyFiles 屬性,雖然它定義的類型是 string,但你千萬不要理解錯,不要把一個文件的路徑賦給它。老周以前就見到一位朋友理解錯了,它誤以為這個屬性是用來設(shè)置復(fù)制程序集文件的緩存路徑,結(jié)果代碼寫了老是不行。唉,這就是不看MSDN的下場。
不要亂來,設(shè)置復(fù)制程序集的緩存目錄是 CachePath 屬性,不是 ShadowCopyFiles 屬性。ShadowCopyFiles 屬性只能用兩個字符串的值,如果要啟用影像復(fù)制,就設(shè)置為 true,如果想禁用,就設(shè)置 false 或者干脆保持默認(rèn)的null值。也就說,它是一個用字符串表示的 bool 值。
下面,我們用一個例子來表演一下,很精彩的。
首先,弄一個類庫項目,然后在里面寫一個全宇宙最簡單的類。
namespace TestLib {