2017年11月27日 星期一
Serilog 初試心得(rookie's Note)

前言:
Serilog是一款給.NET平台的LOG Library,它最大的特色應該是使用事件來驅動,而不是單純的寫下LOG,這點讓它跟其他的Library有很大的不同,而他所提供的多樣Sink讓這點的發揮更加強大。他可以做到同時記下Log,發送信件,存到DB並且傳到Slack中通知維運人員,這些事情都可以在記下Log時一併做完,只需要設定一次。這點是我覺得這個Library最強大的地方。
Source Code
內文:
Serilog除了可以在.NET平台使用外,他還支援了.NET Core,這讓它在非Windows平台上的使用是可能的,Source Code跟以下的文章就是在MAC環境下搭配.NET Core使用Serilog。
開啟一個新的專案後在Dependencies按下右鍵選擇Add Packages,再使用關鍵字「Serilog」查詢~

/// <summary>
/// My logger.
/// </summary>
public class MyLogger
{
/// <summary>
/// The logger.
/// </summary>
private ILogger _logger;
/// <summary>
/// Initializes a new instance of the <see cref="T:BenSerilogNlog.MyLogger"/> class.
/// </summary>
public MyLogger()
{
////最基本的建立Logger的方式
this._logger = new LoggerConfiguration()
.WriteTo.Console()
.CreateLogger();
}
/// <summary>
/// Logs the info.
/// </summary>
/// <param name="message">Message.</param>
public void LogInfo(string message){
this._logger.Information(message);
}
}
呼叫端可以這樣使用
//// new 一個實體
var myLogger = new MyLogger();
//// 呼叫 Serilog 的 ILogger 介面的方法(紀錄 Info等級的Log)
myLogger.LogInfo("Information");
Console.Read();
這樣就是一個最簡單的Log使用方法。
若要調整Log的紀錄格式可以這樣做簡單的設定:
public MyLogger()
{
this._logger = new LoggerConfiguration()
.WriteTo.Console(
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level}] {Message}{NewLine}{Exception}"
)
.CreateLogger();
}
以簡單的情境來說,這樣其實就可以做到跟NLog一樣的事情。
2017年6月11日 星期日
Git on Windows a rookie tutorial - Ben's Version

前言:
使用Git也有一段時間了,但是一直以來都很依賴使用GUI來幫忙做事情,甚少使用指令。
在一次GUI改版後因為使用GUI的回應速度真的是太慢了,所以才開始學習使用指令來做事,也是這時才發現原來使用指令沒有想像中麻煩,也如預期中的非常快速。
因此這邊就我自己的使用心得來分享,也因為我在使用git指令時有很多設定是已經在安裝GUI時就設定完畢的,所以這邊的常用指令其實是就我個人使用上而言。
像是有很多remote repository的設定我不是使用指令來完成的,這部分的指令就之後有機會再補上了。
1)安裝
下載路徑:https://git-for-windows.github.io/
GUI:https://git-scm.com/downloads/guis
下載安裝檔後基本上用預設值並一直按下下一步就好

[圖 Install-1]

[圖 Install-2]

[圖 Install-3]
在Powershell更輕易的使用Git(posh-git):
使用PsGet安裝
PsGet => Powershell安裝套件的好幫手
posh-git => 在powershell使用git的好幫手

[圖 Install-4]
但若使用這種方式安裝,每次關掉powershell要再使用就需要先下個
若不想這麼麻煩的話,打開一個新的powershell prompt直接輸入
2)產生SSH Key(非Windows環境使用格式,適合用在command line環境中使用)
在Windows下自產SSH Key,而且不是Windows使用的格式我們需要使用剛剛安裝Git for windows時一起安裝的Git Bash環境來操作會比較方便。
若是產建Github所需要的SSH Key可以直接參考Github的說明: https://help.github.com/articles/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent/
2-1)
b則是密鑰長度,基本推薦2048或4096
t則是使用的加密算法,Github使用的是RSA演算法
2-2)選擇存放位置與設定passphrase

[圖 SSHKey-1]
執行完成後你應該就會看到指定路徑上面會有兩個檔案,副檔名帶有pub字樣的就是public key,這兩個都是純文字檔案可以用notepad打開看看裡面的內容
3)常用指令
3-1)add [告訴git哪些檔案要列入追蹤]
常用指令之一,把指定的檔案放入staged area中,因為commit只會把staged area的檔案遷入。
把所有有異動的檔案遷入
3-2)commit [讓進入stage area的異動變簽入git]
常用指令之一,git的commit message其實可以接受多行,只要不要打最後一個單引號,按下enter的話就會跳到下一行去,想要結束的話就把單引號補上去即可。
3-3)branch [git的強大之處 branch超好開,開免錢]
可以把本機的branch都羅列出來,算是方便一次瀏覽目前該repository的branch狀態。
常指令之一,通常建議branch名稱前面會多帶一層方便做管理,這個範例中feature就是這個branch的上一層名稱,在GUI (ex. SourceTree)中通常會用資料夾來表示。

[圖 Branch-1]
3-4)stash [保留做到一半的想法]
把該repository所有stash列出來
將所有未入staged area的異動存入stash中

[圖 Stash-1]
3-5)checkout [切換branch]
常用指令之一,將目前的pointer所在位置切換到指定的branch身上
常用指令之一,會做兩件事情,先產生一個新的branch,二是將pointer切換到該branch身上

[圖 Checkout-1]

[圖 Checkout-2]
3-6)remote [設定遠端repository]
將遠端的ref資訊顯示出來,在圖示中因為這個repository是從githubclone來的所以會顯示ref來源

[圖 Remote-1]
3-7)push [將成果交付出去]
將local branch develop 的異動推到 remote branch develop
指令型態:git push
3-8)log [看看歷史過程]
將branch的log以單行印出

[圖 Log-2]
將branch的log以圖形印出

[圖 Log-2]
使用漂亮的格式印出
4)使用GUI介面
基本上到這個連結選一個自己喜歡的使用就好,只要懂Git的指令就不難理解那些介面的操作。
而個人在GUI的推薦上,就推薦 SourceTree與GitKraken這兩套GUI,其他不是不好用事這兩套太威了。
若是公司的流程有Git-flow的話則推薦SourceTree,若不是使用Git-flow的話則推間使用GitKraken這套,這是因為SourceTree本身自帶git flow套件,與Git -Flow流程整合得很好。
使用Git也有一段時間了,但是一直以來都很依賴使用GUI來幫忙做事情,甚少使用指令。
在一次GUI改版後因為使用GUI的回應速度真的是太慢了,所以才開始學習使用指令來做事,也是這時才發現原來使用指令沒有想像中麻煩,也如預期中的非常快速。
因此這邊就我自己的使用心得來分享,也因為我在使用git指令時有很多設定是已經在安裝GUI時就設定完畢的,所以這邊的常用指令其實是就我個人使用上而言。
像是有很多remote repository的設定我不是使用指令來完成的,這部分的指令就之後有機會再補上了。
1)安裝
下載路徑:https://git-for-windows.github.io/
GUI:https://git-scm.com/downloads/guis
下載安裝檔後基本上用預設值並一直按下下一步就好

[圖 Install-1]

[圖 Install-2]

[圖 Install-3]
在Powershell更輕易的使用Git(posh-git):
使用PsGet安裝
PsGet => Powershell安裝套件的好幫手
posh-git => 在powershell使用git的好幫手
(new-object Net.WebClient).DownloadString("http://psget.net/GetPsGet.ps1") | iex
Install-Module posh-git

[圖 Install-4]
但若使用這種方式安裝,每次關掉powershell要再使用就需要先下個
Import-Module posh-git才能使用。
若不想這麼麻煩的話,打開一個新的powershell prompt直接輸入
Add-PoshGitToProfile這樣就可以免除每次關掉都要在import module的窘境。
2)產生SSH Key(非Windows環境使用格式,適合用在command line環境中使用)
在Windows下自產SSH Key,而且不是Windows使用的格式我們需要使用剛剛安裝Git for windows時一起安裝的Git Bash環境來操作會比較方便。
若是產建Github所需要的SSH Key可以直接參考Github的說明: https://help.github.com/articles/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent/
2-1)
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"C這個參數只是給的commant可加可不加,只是加上去方便做辨識
b則是密鑰長度,基本推薦2048或4096
t則是使用的加密算法,Github使用的是RSA演算法
2-2)選擇存放位置與設定passphrase

[圖 SSHKey-1]
執行完成後你應該就會看到指定路徑上面會有兩個檔案,副檔名帶有pub字樣的就是public key,這兩個都是純文字檔案可以用notepad打開看看裡面的內容
3)常用指令
3-1)add [告訴git哪些檔案要列入追蹤]
常用指令之一,把指定的檔案放入staged area中,因為commit只會把staged area的檔案遷入。
git add file_locatioon
把所有有異動的檔案遷入
git add .
3-2)commit [讓進入stage area的異動變簽入git]
常用指令之一,git的commit message其實可以接受多行,只要不要打最後一個單引號,按下enter的話就會跳到下一行去,想要結束的話就把單引號補上去即可。
git commit -m 'your commit message'
3-3)branch [git的強大之處 branch超好開,開免錢]
可以把本機的branch都羅列出來,算是方便一次瀏覽目前該repository的branch狀態。
git branch --list
常指令之一,通常建議branch名稱前面會多帶一層方便做管理,這個範例中feature就是這個branch的上一層名稱,在GUI (ex. SourceTree)中通常會用資料夾來表示。
git branch feature/my_branch_name
[圖 Branch-1]
3-4)stash [保留做到一半的想法]
把該repository所有stash列出來
git stash list
將所有未入staged area的異動存入stash中
git stash save 'your stash message'

[圖 Stash-1]
3-5)checkout [切換branch]
常用指令之一,將目前的pointer所在位置切換到指定的branch身上
git checkout [your_branch_name]
常用指令之一,會做兩件事情,先產生一個新的branch,二是將pointer切換到該branch身上
git checkout -b [your_branch_name]
[圖 Checkout-1]
[圖 Checkout-2]
3-6)remote [設定遠端repository]
將遠端的ref資訊顯示出來,在圖示中因為這個repository是從githubclone來的所以會顯示ref來源
git remote -v show
[圖 Remote-1]
3-7)push [將成果交付出去]
將local branch develop 的異動推到 remote branch develop
指令型態:git push
git push origin develop develop
3-8)log [看看歷史過程]
將branch的log以單行印出
git log --oneline
[圖 Log-2]
將branch的log以圖形印出
git log --graph
[圖 Log-2]
使用漂亮的格式印出
git log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit
4)使用GUI介面
基本上到這個連結選一個自己喜歡的使用就好,只要懂Git的指令就不難理解那些介面的操作。
而個人在GUI的推薦上,就推薦 SourceTree與GitKraken這兩套GUI,其他不是不好用事這兩套太威了。
若是公司的流程有Git-flow的話則推薦SourceTree,若不是使用Git-flow的話則推間使用GitKraken這套,這是因為SourceTree本身自帶git flow套件,與Git -Flow流程整合得很好。
2017年3月27日 星期一
使用XmlTextWriter時用UTF8編碼預設有BOM

前言:
會有這個文章主要是因為,有需要比對序列化後字串是否符合預期的情境,但是卻因為沒有注意到這點讓測試程式碼怎樣都過不了 Orz
這個問題坦白說,若沒有踩到還真不會特別去計較這件事。關於BOM(位元組順序記號),就我的實務經驗來講是很少會使用到的資訊,尤其是最常用到的編碼方式為UTF-8,一般來說我不會馬上想到這個方向。
而UTF-8 with BOM的檔案基本上跟 without BOM用肉眼是看不出差異的,只會在使用游標左右移動時發現需要多按一下才會動,在精神不濟時會以為這些都是幻覺 XD。但若是用Notepad++的話,可以開啟16進位的檢視,就能看到UTF-8 BOM的那該死的位元組「ef bb bf」,在windows平台上有時可以看到存檔時可以選擇是否要包含BOM這個選項。
實驗:
在 XmlTextWriter 實體要 new 出來時會有幾種作法,其中我們要講的就是把Encoding方式傳入的那種。
文字是否有加入BOM一般來說很難用肉眼看出來有什麼差異,因此我們使用GetByteCount來識別是否有BOM參入其中,看看是否真的多了那三個該死的位元標記符號。
在驗證的程式碼我們這樣寫:
執行過後就會發現,我們在.NET平台最常使用到的呼叫方式就是那種會帶入BOM的那種。
這點我們可以從原始碼中略窺一二
在實測之中這個參數不只會影響GetPreamble這個方法的回傳值,也會影響到輸出的文字是否會含有BOM。
會寫這篇主要是這個問題說大不大,但若踩到的話也可以吃足苦頭,寫下來避免下次遇到時問題找個老半天,才發現是這個編碼問題。
2017.03.30補充:
關於UTF-8編碼的檔案是否要帶BOM的問題,在stackoverflow的這篇討論串還不錯~
會有這個文章主要是因為,有需要比對序列化後字串是否符合預期的情境,但是卻因為沒有注意到這點讓測試程式碼怎樣都過不了 Orz
這個問題坦白說,若沒有踩到還真不會特別去計較這件事。關於BOM(位元組順序記號),就我的實務經驗來講是很少會使用到的資訊,尤其是最常用到的編碼方式為UTF-8,一般來說我不會馬上想到這個方向。
而UTF-8 with BOM的檔案基本上跟 without BOM用肉眼是看不出差異的,只會在使用游標左右移動時發現需要多按一下才會動,在精神不濟時會以為這些都是幻覺 XD。但若是用Notepad++的話,可以開啟16進位的檢視,就能看到UTF-8 BOM的那該死的位元組「ef bb bf」,在windows平台上有時可以看到存檔時可以選擇是否要包含BOM這個選項。
實驗:
在 XmlTextWriter 實體要 new 出來時會有幾種作法,其中我們要講的就是把Encoding方式傳入的那種。
public class SerializeHelper { ////// 序列化 /// /// 物件 /// 編碼 ///序列化後文字 public string Serialize(object o, Encoding encoding) { XmlSerializer xmls = new XmlSerializer(o.GetType()); var xns = new XmlSerializerNamespaces(); xns.Add(string.Empty, string.Empty); using (MemoryStream ms = new MemoryStream()) { using (var writer = new XmlTextWriter(ms, encoding)) { xmls.Serialize(writer, o, xns); } string xml = Encoding.UTF8.GetString(ms.ToArray()); return xml; } } }
文字是否有加入BOM一般來說很難用肉眼看出來有什麼差異,因此我們使用GetByteCount來識別是否有BOM參入其中,看看是否真的多了那三個該死的位元標記符號。
在驗證的程式碼我們這樣寫:
class Program { static void Main(string[] args) { var demonItem = new DemonClass() { Address = "Taipei", Id = 1, Name = "Ben", Refer = new DemonClass() }; var serialize = new SerializeHelper(); //// Encoding.UTF8預設會帶BOM var stringObj = serialize.Serialize(demonItem, Encoding.UTF8); Console.WriteLine("stringObj:" + Encoding.UTF8.GetByteCount(stringObj)); var stringObj2 = serialize.Serialize(demonItem, new UTF8Encoding(false)); Console.WriteLine("stringObj2:" + Encoding.UTF8.GetByteCount(stringObj2)); } }
執行過後就會發現,我們在.NET平台最常使用到的呼叫方式就是那種會帶入BOM的那種。
這點我們可以從原始碼中略窺一二
public static Encoding UTF8 { [__DynamicallyInvokable] get { if (Encoding.utf8Encoding == null) { Encoding.utf8Encoding = new UTF8Encoding(true); } return Encoding.utf8Encoding; } }而在MSDN中 UTF8Encoding 的建構子資訊中提到這個參數會影響到 GetPreamble 這個方法的行為。
在實測之中這個參數不只會影響GetPreamble這個方法的回傳值,也會影響到輸出的文字是否會含有BOM。
會寫這篇主要是這個問題說大不大,但若踩到的話也可以吃足苦頭,寫下來避免下次遇到時問題找個老半天,才發現是這個編碼問題。
2017.03.30補充:
關於UTF-8編碼的檔案是否要帶BOM的問題,在stackoverflow的這篇討論串還不錯~
2016年12月5日 星期一
Autofac memory leak experiment

前言:
現在有很多系統除非太小,不然應該都會使用第三方開發的DI容器,來實現IoC。但是使用DI容器時免不了要對於物件的生命週期有一定的了解,不然到時候出現相關的問題就手忙腳亂了。因此這一篇文章就記錄了我使用Autofac時,遇到的memory leak的問題,並把所做的實驗記錄下來。
實驗的情境主要是run在console環境中,模擬類似windows service的情境,所以web的asp.net環境並不在此實驗的考慮範圍內。
這篇文章使用的原始碼連結如下:
GitHub repo url: https://github.com/RiceBen/AutofacExperiment
內文:
物件的生命週期與最初註冊物件的依賴是怎樣的方式,預設是InstancePerDependency這個方法,它會確保你每次呼叫Resolve方法時可以取得新的物件,並不會與其他呼叫端使用同一份實體。這個特性在大多數的時候都會符合需求。
因此在注入時我們先這樣寫:
ps.快照時間基本上都是10秒30秒各拍一次,執行超過50秒就結束實驗。
實驗一:
介面:
就可以開始觀察記憶體的使用了。
Profiler畫面(執行50+秒):
執行畫面:
從這個執行畫面中我們可以看到一個現象,該實體的數字永遠都是回1,表示每次取得的實體都是與上一個不同的實體。
實驗結果一:雖然有呼叫dispose但是還是可以看到記憶體不斷飆升(兩份快照相比後者比前者多了20以上的物件),因此可以預期的是不斷執行後一定會發生OOM的例外訊息
實驗二:
我們改用single instance看看是否真的只會有那麼一個實體被生成出來
呼叫端則這樣寫:
Profiler畫面(執行50+秒):
可以看得到兩份快照並無增加的物件,表示並不會隨著時間增加而讓物件在記憶體中不斷產生。
執行畫面:
從這個畫面中我們可以看出每次Number屬性回應的數字都會累加,表明就算呼叫dispose後,依舊可以取得同樣一份實體。
實驗結果二: 有呼叫dispose,不過也因為autofac握有一份參考,所以應該還是沒有真正釋放物件,不過因為依賴方式使用SingleInstance,所以執行再久並沒有memory leak的問題發生。
實驗三:
這次我們改用InstancePerLifetimeScope這個依賴方式
呼叫端我們這樣寫:
Profiler畫面(執行50+秒):
執行畫面:
這邊可以看到Number回應的數字不斷增長,表示就算有呼叫dispose方法,但是取得的實體還是同樣一份。
實驗解果三:有呼叫dispose,但是因為autofac擁有一份參考,所以應該沒有真正釋放次件,但是應為依賴方式為InstancePerLifetimeScope所以並不會產生新的實體出來占用記憶體。
實驗四:
我們回到一開始的物件依賴方式
但是接下來我們的呼叫端就改成這樣:
Profiler畫面(執行50+秒):
雖然圖示看起來記憶體有偏高,但是兩份快照顯示物件並沒有增加。
執行畫面:
這個實驗我們可以發現Dispose方法被呼叫了兩次!一次是內圈的childScope結束using範圍時呼叫,另一次是外圈的lifetimescope結束using範圍時呼叫!而Number回應的數字也都是1,表明了每次都是取得新的實體。
實驗結果四:我們可以發現dispose有被呼叫,而且記憶體也沒有逐漸上升,代表每個新產生的物件有確實的被回收了!這是因為我們這次的using是包在lifetimescope中,而不是產生出來的實體,在lifetimescope結束的當下,他會負責把在他當中產生的實體也一起回收,而在沒有人握有實體的參照,又呼叫dispose時GC就會把這份實體的資源給回收回去,因此就不會讓記憶體持續累積下去。
以上四組實驗我們可以發現一個基本的autofac在物件的生命週期中,所造成的影響。關於這個主題還有很多可以探討的問題,像是官網中還提到的另外幾種依賴的方式,還有不同的依賴方式適合用在什麼情境中,還有不同的應用系統適合用什麼樣的依賴方式。
對於Autofac有研究的人,歡迎在下方留言或是寄信給我一起討論,謝謝~!
現在有很多系統除非太小,不然應該都會使用第三方開發的DI容器,來實現IoC。但是使用DI容器時免不了要對於物件的生命週期有一定的了解,不然到時候出現相關的問題就手忙腳亂了。因此這一篇文章就記錄了我使用Autofac時,遇到的memory leak的問題,並把所做的實驗記錄下來。
實驗的情境主要是run在console環境中,模擬類似windows service的情境,所以web的asp.net環境並不在此實驗的考慮範圍內。
這篇文章使用的原始碼連結如下:
GitHub repo url: https://github.com/RiceBen/AutofacExperiment
內文:
物件的生命週期與最初註冊物件的依賴是怎樣的方式,預設是InstancePerDependency這個方法,它會確保你每次呼叫Resolve方法時可以取得新的物件,並不會與其他呼叫端使用同一份實體。這個特性在大多數的時候都會符合需求。
因此在注入時我們先這樣寫:
ps.快照時間基本上都是10秒30秒各拍一次,執行超過50秒就結束實驗。
實驗一:
public class DAModules : Autofac.Module { protected override void Load(ContainerBuilder builder) { var assembly = Assembly.Load("NineYi.ERP.DA.ERPDB"); builder.RegisterType<deamonresourcerepository>() .As<ideamonresourcerepository>() .InstancePerDependency(); } }接下來就是我們這次的實驗目標的寫法:
介面:
///實作:/// Iinterface of deamon resource repository /// public interface IDeamonResourceRepository: IDisposable { ////// Eat resource /// void ResourceMonster(); }
///接下來我們在呼叫端這樣寫:/// Deamon resource repository. /// /// <seealso cref="NineYi.ERP.DA.ERPDB.Deamon.IDeamonResourceRepository" /> /// <seealso cref="System.IDisposable" /> public class DeamonResourceRepository : IDeamonResourceRepository { ////// Gets the name. /// ////// The name. /// public string Name { get { return typeof(DeamonResourceRepository).FullName; } } ////// 序號 (為了輔助驗證取得的實體) /// public int Number { get; set; } ////// 執行與釋放 (Free)、釋放 (Release) 或重設 Unmanaged 資源相關聯之應用程式定義的工作。 /// public void Dispose() { Console.WriteLine(string.Format("{0} dispose!", this.Name)); } ////// Eat resource /// public void ResourceMonster() { this.Number += 1; Console.WriteLine(string.Format("DeamonResource Number:{0}", this.Number)); } }
static void Main(string[] args) { var builder = new ContainerBuilder(); builder.RegisterModule<NineYi.ERP.DA.ERPDB.Modules.DAModules>(); using (var container = builder.Build()) { MemoryLeakMethod02(container); } } ///接下來我們開啟Visual Studio的 Analyze -> Performance Profiler -> 勾選memory usage/// 容易不自覺產生memory的語法 /// /// The container.////// 重點是使用InstancePerDependency作為物件的生命週期,這會導致每次都會產生一個完全新的實體出來,就算有呼叫dispose,但是autofac會握有一份參考,導致這份實體並不會被GC真正回收。 /// static void MemoryLeakMethod02(IContainer container) { using (var lifetimescope = container.BeginLifetimeScope()) { while (1 == 1) { using (var resource = lifetimescope.Resolve<NineYi.ERP.DA.ERPDB.Deamon.IDeamonResourceRepository>()) { resource.ResourceMonster(); System.Threading.Thread.Sleep(500); } } } }
就可以開始觀察記憶體的使用了。
Profiler畫面(執行50+秒):
執行畫面:
從這個執行畫面中我們可以看到一個現象,該實體的數字永遠都是回1,表示每次取得的實體都是與上一個不同的實體。
實驗結果一:雖然有呼叫dispose但是還是可以看到記憶體不斷飆升(兩份快照相比後者比前者多了20以上的物件),因此可以預期的是不斷執行後一定會發生OOM的例外訊息
實驗二:
我們改用single instance看看是否真的只會有那麼一個實體被生成出來
protected override void Load(ContainerBuilder builder) { builder.RegisterType<deamonresourcerepository>() .As<ideamonresourcerepository>() .SingleInstance(); }
呼叫端則這樣寫:
static void Main(string[] args) { var builder = new ContainerBuilder(); builder.RegisterModule<NineYi.ERP.DA.ERPDB.Modules.DAModules>(); using (var container = builder.Build()) { MemoryLeakMethod02(container); } } ////// 與實驗一是一樣的寫法,只有物件的生命中期使用方式不同 /// /// The container.////// 重點是使用SingleInstance作為物件的依賴方式,這會讓每次叫用的物件只會生成一份,所以看起來就沒有memory leak的問題發生。 /// static void MemoryLeakMethod02(IContainer container) { using (var lifetimescope = container.BeginLifetimeScope()) { while (1 == 1) { using (var resource = lifetimescope.Resolve<NineYi.ERP.DA.ERPDB.Deamon.IDeamonResourceRepository>()) { resource.ResourceMonster(); System.Threading.Thread.Sleep(500); } } } }
Profiler畫面(執行50+秒):
可以看得到兩份快照並無增加的物件,表示並不會隨著時間增加而讓物件在記憶體中不斷產生。
執行畫面:
從這個畫面中我們可以看出每次Number屬性回應的數字都會累加,表明就算呼叫dispose後,依舊可以取得同樣一份實體。
實驗結果二: 有呼叫dispose,不過也因為autofac握有一份參考,所以應該還是沒有真正釋放物件,不過因為依賴方式使用SingleInstance,所以執行再久並沒有memory leak的問題發生。
實驗三:
這次我們改用InstancePerLifetimeScope這個依賴方式
builder.RegisterType<deamonresourcerepository>() .As<ideamonresourcerepository>() .InstancePerLifetimeScope();依據官方說明文件:這個依賴方式會讓同一個 lifetimescope 中取得相同類別的物件,會取用同一份實體。不同的 lifetimescope 就算呼叫同一個類別也會使用同一份實體,因此我們可以用同樣的情境來驗證是否正確。
呼叫端我們這樣寫:
static void Main(string[] args) { var builder = new ContainerBuilder(); builder.RegisterModule<NineYi.ERP.DA.ERPDB.Modules.DAModules>(); using (var container = builder.Build()) { MemoryLeakMethod02(container); } } ////// 與實驗一是一樣的寫法,只有物件的依賴方式不同 /// /// The container.////// 重點是使用InstancePerLifetimeScope作為物件的依賴方式,同一個scope下取得的實體將會相同。 /// static void MemoryLeakMethod02(IContainer container) { using (var lifetimescope = container.BeginLifetimeScope()) { while (1 == 1) { using (var resource = lifetimescope.Resolve<NineYi.ERP.DA.ERPDB.Deamon.IDeamonResourceRepository>()) { resource.ResourceMonster(); System.Threading.Thread.Sleep(500); } } } }
Profiler畫面(執行50+秒):
執行畫面:
這邊可以看到Number回應的數字不斷增長,表示就算有呼叫dispose方法,但是取得的實體還是同樣一份。
實驗解果三:有呼叫dispose,但是因為autofac擁有一份參考,所以應該沒有真正釋放次件,但是應為依賴方式為InstancePerLifetimeScope所以並不會產生新的實體出來占用記憶體。
實驗四:
我們回到一開始的物件依賴方式
builder.RegisterType<deamonresourcerepository>() .As<ideamonresourcerepository>() .InstancePerDependency();
但是接下來我們的呼叫端就改成這樣:
static void Main(string[] args) { var builder = new ContainerBuilder(); builder.RegisterModule<NineYi.ERP.DA.ERPDB.Modules.DAModules>(); using (var container = builder.Build()) { MemoryLeakMethod02(container); } } ////// 使用Child Scope來管理這區域中產生的物件,可以確保物件在這個區域使用完畢後會被釋放 /// /// ////// 把using的範圍用在Lifetimescope,而不是產生出來的物件 /// private static void NoMemoryLeakMethod02(IContainer container) { using (var lifetimescope = container.BeginLifetimeScope()) { while (1 == 1) { using (var childScope = lifetimescope.BeginLifetimeScope()) { using (var resource = childScope.Resolve<NineYi.ERP.DA.ERPDB.Deamon.IDeamonResourceRepository>()) { resource.ResourceMonster(); System.Threading.Thread.Sleep(500); } } } } }
Profiler畫面(執行50+秒):
雖然圖示看起來記憶體有偏高,但是兩份快照顯示物件並沒有增加。
執行畫面:
這個實驗我們可以發現Dispose方法被呼叫了兩次!一次是內圈的childScope結束using範圍時呼叫,另一次是外圈的lifetimescope結束using範圍時呼叫!而Number回應的數字也都是1,表明了每次都是取得新的實體。
實驗結果四:我們可以發現dispose有被呼叫,而且記憶體也沒有逐漸上升,代表每個新產生的物件有確實的被回收了!這是因為我們這次的using是包在lifetimescope中,而不是產生出來的實體,在lifetimescope結束的當下,他會負責把在他當中產生的實體也一起回收,而在沒有人握有實體的參照,又呼叫dispose時GC就會把這份實體的資源給回收回去,因此就不會讓記憶體持續累積下去。
以上四組實驗我們可以發現一個基本的autofac在物件的生命週期中,所造成的影響。關於這個主題還有很多可以探討的問題,像是官網中還提到的另外幾種依賴的方式,還有不同的依賴方式適合用在什麼情境中,還有不同的應用系統適合用什麼樣的依賴方式。
對於Autofac有研究的人,歡迎在下方留言或是寄信給我一起討論,謝謝~!
訂閱:
文章 (Atom)