程序的熱升級 實現程序的熱升級 - 方式一

實現程序的熱升級 - 方式一

 

前言

所謂熱升級,實際上就是在程序/服務不中止的前提下,經過增長、修改、刪除相關功能模塊,達到功能升級的目的。html

爲何要熱升級

舉個例子,咱們可能都有這樣一個經歷,正在操做一個軟件,多是個重要的工做,這個時候軟件發現有新的功能更新,須要升級程序,彈出一個看似很人性化的提示:請從新啓動程序以完成升級!可是,問題是,升級的功能可能跟咱們當前工做所用的功能徹底沒有關係,卻要咱們丟棄辛辛苦苦作了半天的工做,就爲了一個不相關的功能重作!咱們固然也可不理,繼續作咱們的工做,直到完成後重啓完成升級。但這顯然不是咱們理想的方式,若是軟件是以敏捷開發模式作出的,幾乎不可避免的要頻繁升級程序,那麼能夠想象這會讓用戶多麼煩惱!安全

特別是對於服務來講,咱們老是但願保持穩定,但願7*24小時永不中止。因此,咱們但願能有這樣一種方式,可以直接更新相應的模塊,在不中止進程的狀況下,保持服務爲最新版本。下面我介紹兩種實現方式。網絡

應用程序域(AppDomain)

在正式介紹前,咱們還要在這裏從新溫習一下一個重要的概念,應用程序域。dom

所謂應用程序域,.Net引入的一個概念,指的是一種邊界,它標識了代碼的運行範圍,在其中產生的任何行爲,包括異常都不會影響到其餘應用程序域,起到安全隔離的效果。也能夠當作是一個輕量級的進程。函數

一個進程能夠包含多個應用程序域,各個域之間相互獨立。以下是一個.net進程的組成(圖片來自網絡)post

進程啓動後,會首先創建兩個應用程序域,一個叫公共程序域(Domain-Neutral),其中加載的全部類型能夠供其餘全部應用程序域使用;另外一個叫默認程序域,它加載了咱們本身的應用程序,默認程序域中運行的代碼可能致使整個進程崩潰。爲了使咱們的進程可以穩定運行,就能夠新建一個應用程序域來加載一些咱們認爲可能會致使問題的程序集,而在新的應用程序域中運行的代碼不會對默認程序域形成影響,保證了進程的穩定。ui

使用AppDomain類提供的靜態方法,可進行應用程序域的建立與卸載url

//建立應用程序域
public static AppDomain CreateDomain(string friendlyName);
//卸載應用程序域
public static void Unload(AppDomain domain);

其中,建立應用程序域方法CreateDomain還有其餘多個重載,爲咱們提供豐富的建立新應用程序域所用的配置。spa

卸載應用程序域時,CLR將清理該應用程序域使用的全部資源,包括加載的程序集,未釋放的非託管資源等。但公共程序域和默認程序域沒法卸載。.net

 

一般,咱們封裝的功能都是以程序集的形式存在的,而程序集只有在應用程序域卸載之後才能釋放。這就是爲何在程序運行的過程當中沒法直接進行修改程序文件的緣由,程序運行後,相關的程序集文件被加載到了默認應用程序域中,而默認應用程序域又沒法卸載,致使咱們不得不關閉進程才能修改相應文件。

而咱們在程序啓動的時候,把這個程序集加載一個咱們新建的應用程序域中,須要更新文件的時候只要卸載這個域,在不關閉進程的狀況下,不就能夠對文件進行更新了麼?這個也就是咱們可以進行「熱升級」的理論基礎。

方式一

在新的應用程序域中加載功能模塊,須要更新時,卸載應用程序域,再從新加載。

下面用一個簡單具體的解決方案說明一下。

解決方案以下所示:

 

其中,MainServer是主程序,Module1是程序中要使用的功能模塊,實現了CommonLib項目中ICalculater接口

public interface ICalculater
{
    int Calc(int a, int b);
}
複製代碼
public class Calculater : MarshalByRefObject, ICalculater
{
    public int Calc(int a, int b)
    {
        int res = a + b;

        Console.WriteLine("Add {0} and {1}, result: {2} [run in {3}]",
            a, b, res, AppDomain.CurrentDomain.FriendlyName);

        return res;
    }
}
複製代碼

當前功能是計算兩個整數之和,後面我將經過修改該計算的實現,達到功能升級的目的。

注意:若是要在新的應用程序域中調用,類型必須繼承MarshalByRefObject,請網上參考-按引用封送和按值封送-相關文章。

 

首先,咱們來看如何建立新的程序域,並調用域內方法

複製代碼
static void Main(string[] args)
{
    Console.WriteLine("current domain: {0}", AppDomain.CurrentDomain.FriendlyName + Environment.NewLine);

    Console.WriteLine("input two data to calc: ");
    Console.Write("a: ");
    int a = Convert.ToInt32(Console.ReadLine());
    Console.Write("b: ");
    int b = Convert.ToInt32(Console.ReadLine());

    AppDomain ad_Calc = AppDomain.CreateDomain("domin #calc");
    ICalculater calc = (ICalculater)ad_Calc.CreateInstanceAndUnwrap("Module1", "Module1.Calculater");
    calc.Calc(a, b);
}
複製代碼

 

其實代碼很簡單,經過調用方法CreateDomain建立新的應用程序域並給域命名爲domain #calc後,調用CreateInstanceAndUnwrap,輸入程序集名和要建立的實例類型名後,便可得到一個該類型的一個引用代理。面向接口的方式使咱們能夠直接經過強制轉換調用相應的方法(若是不面向接口的話,就要使用反射的方式調用方法,相對來講麻煩一些,速度也會慢一些)。執行過程當中,讓程序打印出當前所在域的名稱,能夠直觀地看到當前代碼是在哪裏執行的。以下是運行結果

結果中咱們能很清晰的看到,沒有調用Calc方法時,當前運行所在域是MainServer.exe(也便是默認應用程序域的名稱),調用Calc方法時,其實是運行在domain #calc域中的。

既然實現了在新域中運行,接下來咱們看下怎麼對功能進行升級。

繼續在Main函數中添加代碼,在域建立並加載Module1成功後,嘗試直接刪除Module1.dll文件

複製代碼
string cmd;
while ((cmd = Console.ReadLine()).ToLower() != "quit")
{
    switch (cmd.ToLower())
    {
        case "del_calc":
            TryDelete("Module1.dll");
            Console.WriteLine();
            break;
        case "unload_calc":
            UnloadDomain(ad_Calc);
            Console.WriteLine();
            break;
        case "reload_and_run_calc":
            AppDomain domainNew;
            if (ReloadDomain(out domainNew))
            {
                ICalculater calcNew = (ICalculater)domainNew.CreateInstanceAndUnwrap("Module1", "Module1.Calculater");
                calcNew.Calc(a, b);
            }
            Console.WriteLine();
            break;
    }
}
複製代碼

結果顯然是,沒法刪除,Module1.dll文件句柄還在程序域中沒有釋放。

卸載程序域,再刪除試試呢

成功了!也就是卸載程序域後,該程序域加載的文件句柄也相應被釋放,這時候能夠自由地操做文件了,固然也就能夠把咱們新版本的功能替換上去了。

這時候咱們修改Calculater中的Calc方法,返回a,b兩參數之積,並生成新的dll文件

複製代碼
public class Calculater : MarshalByRefObject, ICalculater
{
    public int Calc(int a, int b)
    {
        int res = a * b;

        Console.WriteLine("Multiply {0} and {1}, result: {2} [run in {3}]",
            a, b, res, AppDomain.CurrentDomain.FriendlyName);

        return res;
    }
}
複製代碼

從新建立應用程序域,爲了和以前建立的程序域區分,命名爲domin reload #calc ,並加載Module1,執行Calc方法

比較先後兩次調用的結果,功能升級成功!

 

這裏只是介紹了做爲功能提供模塊或沒有界面的模塊的升級方法,而對於須要做爲主界面的子窗體的模塊如何實現熱升級?我將在下一篇進行介紹。

本節完整的代碼請 點擊下載

 
分類:  AppDomain
相關文章
相關標籤/搜索