又是一篇備忘用的教學文了,這次要介紹的是撰寫插件常常會需要考量的「動態加卸載 dll」機制

會需要用到這個的原因是我目前有工作是需要產出報表的,但是有某些報表需要耗費大量的時間執行後才能產出

而我寫的又是一個獨立的網頁系統,這樣的報表就我等在螢幕前都會等到不耐煩,更別提user了... 一定連等都不想等,說不定還給你多按好幾下產生報表鈕=口=

所以我想了一個機制是作一個服務,那服務每 10 分鐘會去資料庫撈資料,看有沒有報表需要處理,如無,則再繼續進入等待模式,如有,他就動起來開始產生報表並寄送報表到指定的mail位址,但由於我並不想作重覆工,所以有切專案,有個類別庫專門裝那些產生報表的類別及函數,而該服務則會去 call 那個類別庫的函式來產生報表並寄送郵件

這時問題就來了,因為服務是一直執行的,而類別庫由於網頁也會參考使用,我如果新增一個報表的類別,就得關閉服務後才能進行類別庫的編譯,否則他會無法複製到服務底下替換掉,而服務也就沒辦法產出新的報表了,這顯然不是我想要的(既不想要每次編譯就要關閉服務,也不想要服務RUN新報表就丟出例外),所以就去網路上找資料並紀錄下來,以下進入正題。

一開始動態加載,我就想到我另一個專案使用的 Activator.CreateInstanceFrom 函數來產生個體,本來以為把變數釋放掉後 dll 就會解除鎖定,但發現不行,原來 AppDomain 有快取的制度,所以當第二次透過那函數引用該 dll 時,會直接從 cache 拉回來,這顯然不是我所想要的,而且 dll 也會一直被鎖定住

所以我就去找其它方法,透過 Assembly 來載入,還是一樣,然後就在網路上找到有人提供一個解決方案了

透過 Stream 的方式將 dll 全 read 進來成 byte 陣列,然後再透過 Assembly 來載入

透過這樣的方法是實現動態載入了,而且也不會對實體檔案上鎖,但是我前面有提到過在 AppDomain 中有快取機制,所以這些載進來的其實一直都在記憶體裡,直到 AppDomain 結束,這樣的方法是能解決問題但不理想,因為我的服務可能重開電腦他才會被關閉再重開一次,也就是 AppDomain 要到那時才釋放,這樣記憶體也不知會吃掉多少,我連 GC 會不會回收已經沒指標指到的那些 dll 實體會不會回收都不確定了!

所以就繼續找了下一個方法,既然是因為 AppDomain 的快取機制,那我就再創一個 AppDomain 就行了吧,事實證明這樣的確是成功解決了,因為 AppDomain 才能作卸載的動作(Assembly 是不提供卸載的)

既然是我最後的方法,那我就來稍微介紹我整個專案吧,我主要分成四個專案,分別是

  • Common:這裡面放著通用函數及介面,其中在裡面有定義一個 IReport 就是報表的 Interface
  • Report:這裡面放著報表用的類別庫,當然他會參考 Common ,並實作 IReport
  • WebApp:這裡則是放著 ASP.Net 網頁,線上報表頁面都在裡面,其中他會同時參考 Common 及 Report,因為 WebApps 沒有鎖定 dll 的問題,所以就算不用動態加載對編輯環境而言也不會有差異,所以並不需要特別讓他去動態加載報表的類別
  • WinService:這裡放著我撰寫好的服務,基本上一開始,連登入桌面也不用,他就自動開始啟動了,每十分鐘就會查詢一次資料庫,如果有需要產出報表才會去呼叫相對應的報表檔產生函數並將產出的報表寄送出去,其中他會參考 Common 類別

其中因為 Service 是十分鐘才輪一次,在這十分鐘的空檔他不應該對任何編譯造成阻礙,畢竟那十分鐘他在睡覺,而實事是不透過新建 AppDomain 的方式,他就是會鎖定原始的 dll 檔,所以才需要提出這樣的動態加卸載機制

我知道我 using 了不少東西,但因為編輯環境我目前沒有,所以要明天才能用辦公室的電腦快速移掉沒使用到的 using ,或你也可以自己用自己的 Visual Studio 來移除

這類別主要是建構後透過 Load 函數建構一個新的 AppDomainSetup 來儲存 AppDomain 的設定
之後則透過 AppDomain.CreateDomain 函數來建構一個新的 AppDomain 給這類別庫使用

而 UnLoad 方法則將 Load 建構出來的 appDomain 卸載掉,並執行 GC 的命令回收記憶體資源

GetReport 則是真正要呼叫的方法,讓他建立一個 Report 類別(類別的名稱是 typename 控制)的實體,而建構子的參數則是 timeline

透過這樣的方式,服務每十分鐘到時,他只要呼叫這類別的 Load 語法,就能建構出要動態參考的 dll 的 appDomain 了

而在 GetReport 時實作 dll 的類別並回傳給服務,服務只要呼叫他後就能執行指定的類別產生相關的報表了

最後只要透過 UnLoad 中釋放 appDomain ,就可以釋放 dll ,讓他不被鎖定也不會造成記憶體的負擔 (應該吧!?

 

當然,這篇文章或許仍有缺漏或不足,如果覺得哪邊有疑問,歡迎回覆討論!

 

Reference:

MES项目简单总结(技术篇)—— “热替换”及客制化支持的实现(动态加载/卸载程序集)
Assembly動態建立物件說明Load, LoadFrom, LoadFile
C#解决反射资源无法释放,动态加载和卸载DLL

創作者介紹
創作者 MyWCT 的頭像
MyWCT

天翼翔幻

MyWCT 發表在 痞客邦 留言(0) 人氣()