設計過ORM的攻城獅們或多或少都應該考慮過鏈接池,考慮過鏈接池就或多或少會想過計數器....sql
曾經在我設計一套ORM的時候想過這樣一種鏈接池的管理方式:數據庫
初版的設計很是的簡單,直接就是相似於這樣的
ps:如下爲示例代碼,用意是便於理解,請不要太較真多線程
class MyConnection { public IDbConnection Connection { get; private set; } int _linkedCount; public void Add() { var i = Interlocked.Increment(ref _linkedCount); if (i == 1) { Connection.Open(); } } public void Remove() { var i = Interlocked.Decrement(ref _linkedCount); if (i == 0) { Connection.Close(); } } } class ORM { public MyConnection Connection { get; private set; } public int ExecuteNonQuery(string sql) { try { Connection.Add(); var cmd = Connection.Connection.CreateCommand(); cmd.CommandText = sql; return cmd.ExecuteNonQuery(); } finally { Connection.Remove(); } } }
使用app
using (ORM db = new ORM()) { db.ExecuteNonQuery("insert xxx,xxx,xx"); }
可是緊接着就出現一個問題了
若是我有一個方法,須要同時進行多個操做
好比ide
using (ORM db = new ORM()) { db.ExecuteNonQuery("insert aaa"); db.ExecuteNonQuery("insert bbb"); db.ExecuteNonQuery("insert ccc"); db.ExecuteNonQuery("insert ddd"); }
這樣其實已經開啓關閉了4次數據庫函數
這樣的性能損耗是很是大的性能
因此我考慮這樣的模式測試
using (ORM db = new ORM()) { db.Open(); db.ExecuteNonQuery("insert aaa"); db.ExecuteNonQuery("insert bbb"); db.ExecuteNonQuery("insert ccc"); db.ExecuteNonQuery("insert ddd"); db.Close(); }
這樣有經驗的朋友一眼就能夠看出更大的問題優化
若是insert ddd的報錯了怎麼辦 Close就沒法關閉了
換一種方式說,若是coder忘記寫Close(),或者某個分支中忘記寫Close()怎麼辦?this
難道我要求全部coder都要寫try..finally..?
也許你會說把Close放到using的Dispose方法中去
class ORM : IDisposable { public MyConnection Connection { get; private set; } public int ExecuteNonQuery(string sql) { try { Connection.Add(); var cmd = Connection.Connection.CreateCommand(); cmd.CommandText = sql; return cmd.ExecuteNonQuery(); } finally { Connection.Remove(); } } public void Open() { Connection.Add(); } public void Close() { Connection.Remove(); } public void Dispose() { Close(); } }
可是,若是這樣 coder已經寫了Close() 或者根本沒寫Open() 不是會多觸發一個Remove()?
那豈不是會出現計數器=-1,-2...-N
其實我也不記得我嘗試過多少種方案了,我只記得最終我是這樣實現我想要的效果的:
var token = Connection.Add(); //計數器+1 ... ... Connection.Remove(token); //計數器-1 Connection.Remove(token); //無效果 Connection.Remove(token); //無效果
爲了更加優化這樣的效果,我將Add()的返回值設置爲IDisposable
也就是說能夠這樣寫
using (Connection.Add()) { //... //... }
或者這樣寫
var token = Connection.Add(); //... //... token.Dispose();
var token1 = Connection.Add(); //計數器+1 var token2 = Connection.Add(); //無效果 var token3 = Connection.Add(); //無效果 //... //... Connection.Remove(token3); //無效果 Connection.Remove(token2); //無效果 Connection.Remove(token1); //計數器-1
須要實現這個效果,就必須利用LocalDataStoreSlot對象
/// <summary> 用於儲存多線程間的獨立數據 /// </summary> private LocalDataStoreSlot _dataSlot = Thread.AllocateDataSlot(); /// <summary> 增長引用,並獲取用於釋放引用的標記 /// </summary> public IDisposable Add() { //若是已經存在,則不計數 if (Thread.GetData(_dataSlot) != null)//若是變量值已經存在,則說明當前線程已經執行Add方法,則返回null { return null; } Thread.SetData(_dataSlot, string.Empty);//在當前線程中保存一個變量值 return new CounterToken(this); } /// <summary> 減小引用 /// </summary> /// <param name="token">經過Add方法獲取的標記對象</param> public void Reomve(IDisposable token) { if (token == null) { return; } if (token is CounterToken == false) { throw new ArgumentException("參數不是一個有效的引用標記", "token"); } if (token.Equals(this) == false)//CounterToken已經重寫Equals方法 { throw new ArgumentOutOfRangeException("token", "此標記不屬於當前計數器"); } token.Dispose(); }
其中CounterToken就是計數器的標記,實現IDisposable接口,是一個內部類
經過這樣2部步設置,就能夠實現以前沒法完成的效果了
而ORM部分的代碼須要稍微修改下
class ORM : IDisposable { public MyConnection Connection { get; private set; } public int ExecuteNonQuery(string sql) { //try //{ //Connection.Add(); using (Connection.Add()) { var cmd = Connection.Connection.CreateCommand(); cmd.CommandText = sql; return cmd.ExecuteNonQuery(); } //} //finally //{ // Connection.Remove(); //} //return -1; } IDisposable _counterToken; public void Open() { if (_counterToken == null) { _counterToken = Connection.Add(); } } public void Close() { Connection.Remove(_counterToken); _counterToken = null; } public void Dispose() { Close(); Connection = null; } }
調用的時候
using (ORM db = new ORM()) { db.Open(); db.ExecuteNonQuery("insert aaa"); db.ExecuteNonQuery("insert bbb"); db.ExecuteNonQuery("insert ccc"); db.ExecuteNonQuery("insert ddd"); db.Close(); }
徹底沒有問題,只有一個Open()會增長計數器,最後一個Close()會減小計數器(若是有必要的話,他們會自動打開和關閉Connection());
關鍵的是,這樣作我獲得了一個額外的好處;
即便coder即忘記了using,也忘記了Close...
不要緊,由於GC的存在,一旦CounterToken沒有被任何人應用而釋放掉了,那麼計數器仍然會將他減掉;
最後的最後,我把這個計數器從MyConection中獨立出來了(其實根本就不存在什麼MyConection,都是我瞎編的,只是這樣說比較好理解而已,哈哈~~)
計數器分爲2個模式 ,以前文章中介紹的都是多線程模式,單線程模式只是附帶的一個功能而已
單線程模式:不管在任何線程中每次執行Add方法都會增長引用數,執行Remove或者token.Dispose都會減小引用數
多線程模式:在相同線程中,只有第一次執行Add方法時增長引用數,也只有此token被Remove或Dispose纔會減小引用數
ps:爲了使計數器和數據庫組件解耦,因此我在計數器中設計了一個ValueChaged事件
using System; using System.Collections.Generic; using System.Text; using System.Threading; namespace blqw { /// <summary> 計數器,具備單線程模式和多線程模式 /// </summary> public sealed class Counter { /// <summary> 構造一個計數器,默認單線程模式 /// <para>不管在任何線程中每次執行Add方法都會增長引用數,執行Remove或者token.Dispose都會減小引用數</para> /// </summary> public Counter() :this(false) { Console.WriteLine(); } /// <summary> 構造一個計數器,根據參數multiThreadMode肯定是否使用多線程模式 /// <para>多線程模式:在相同線程中,只有第一次執行Add方法時增長引用數,也只有此token被Remove或Dispose纔會減小引用數</para> /// </summary> /// <param name="multiThreadMode"></param> public Counter(bool multiThreadMode) { if (multiThreadMode) { _dataSlot = Thread.AllocateDataSlot(); } } /// <summary> 當前引用數 /// </summary> private int _value; /// <summary> 值改變事件 /// </summary> private EventHandler<CounterChangedEventArgs> _valueChanged; /// <summary> 用於儲存多線程間的獨立數據,多線程模式下有值 /// </summary> private LocalDataStoreSlot _dataSlot; /// <summary> 增長引用,並獲取用於釋放引用的標記 /// </summary> public IDisposable Add() { if (_dataSlot != null) { //獲取當前線程中的值,此方法每一個線程中得到的值都不一樣,不須要線程同步 //若是已經存在,則不計數 if (Thread.GetData(_dataSlot) != null) { return null; } Thread.SetData(_dataSlot, string.Empty); } return new CounterToken(this); } /// <summary> 減小引用 /// </summary> /// <param name="token">經過Add方法獲取的標記對象</param> public void Remove(IDisposable token) { if (token == null) { return; } if (token is CounterToken == false) { throw new ArgumentException("參數不是一個有效的引用標記", "token"); } if (token.Equals(this) == false) { throw new ArgumentOutOfRangeException("token", "此標記不屬於當前計數器"); } token.Dispose(); } /// <summary> 當前計數值 /// </summary> public int Value { get { return _value; } } /// <summary> 增長記數 /// </summary> private void OnIncrement() { var val = Interlocked.Increment(ref _value); OnValueChanged(val, val - 1); } /// <summary> 減小計數 /// </summary> private void OnDecrement() { if (_dataSlot != null) { Thread.SetData(_dataSlot, null); } var val = Interlocked.Decrement(ref _value); OnValueChanged(val, val + 1); } /// <summary> 觸發ValueChaged事件 /// </summary> /// <param name="value">觸發Value事件時Value的值</param> /// <param name="oldValue">觸發Value事件以前Value的值</param> private void OnValueChanged(int value, int oldValue) { var handler = _valueChanged; if (handler != null) { var e = new CounterChangedEventArgs(value, oldValue); handler(this, e); } } /// <summary> 計數器值改變事件 /// </summary> public event EventHandler<CounterChangedEventArgs> ValueChanged { add { _valueChanged -= value; _valueChanged += value; } remove { _valueChanged -= value; } } /// <summary> 計數器引用標記,調用計數器的Add方法可得到該對象,釋放對象時,減小計數器的計數值 /// </summary> sealed class CounterToken : IDisposable { /// <summary> 宿主計數器 /// </summary> private Counter _counter; /// <summary> 釋放標記,0未釋放,1已釋放,2執行了析構函數 /// </summary> private int _disposeMark; /// <summary> 構造函數,建立引用標記並增長宿主計數器的值 /// </summary> /// <param name="counter">宿主計數器</param> public CounterToken(Counter counter) { if (counter == null) { throw new ArgumentNullException("counter"); } _counter = counter; _counter.OnIncrement(); _disposeMark = 0; } /// <summary> 析構函數 /// </summary> ~CounterToken() { //若是還沒有釋放對象(標記爲0),則將標記改成2,不然標記不變 Interlocked.CompareExchange(ref _disposeMark, 2, 0); Dispose(); } /// <summary> 釋放引用標記,並減小宿主計數器的值 /// </summary> public void Dispose() { //若是已釋放(標記爲1)則不執行任何操做 if (_disposeMark == 1) { return; } //將標記改成1,並返回修改以前的值 var mark = Interlocked.Exchange(ref _disposeMark, 1); //若是當前方法被多個線程同時執行,確保僅執行其中的一個 if (mark == 1) { return; } //釋放Counter引用數 try { _counter.OnDecrement(); } catch { } _counter = null; //若是mark=0,則通知系統不須要執行析構函數了 if (mark == 0) { GC.SuppressFinalize(this); } } /// <summary> 從新實現比較的方法 /// </summary> /// <param name="obj"></param> /// <returns></returns> public override bool Equals(object obj) { if (obj is Counter) { return object.ReferenceEquals(this._counter, obj); } return object.ReferenceEquals(this, obj); } } } /// <summary> 計數器值改變事件的參數 /// </summary> public class CounterChangedEventArgs:EventArgs { internal CounterChangedEventArgs(int value,int oldValue) { Value = value; OldValue = oldValue; } /// <summary> 當前值 /// </summary> public int Value { get; private set; } /// <summary> 原值 /// </summary> public int OldValue { get; private set; } } }
var counter = new Counter(true);//多線程模式 //var counter = new Counter(); //單線程模式 new Thread(() => { using (counter.Add()) //計數器+1 當前計數器=1 { Console.WriteLine("線程a:" + counter.Value); using (counter.Add()) //計數器不變 當前計數器=1 { Console.WriteLine("線程a:" + counter.Value); using (counter.Add()) //計數器不變 當前計數器=1 { Console.WriteLine("線程a:" + counter.Value); Thread.Sleep(100); //等待線程b執行,b執行完以後 當前計數器=1 } //計數器不變 當前計數器=1 Console.WriteLine("線程a:" + counter.Value); } //計數器不變 當前計數器=1 Console.WriteLine("線程a:" + counter.Value); } //計數器-1 當前計數器=0 Console.WriteLine("線程a:" + counter.Value); }).Start(); Thread.Sleep(50); new Thread(() => { var token1 = counter.Add(); //計數器+1 當前計數器=2 Console.WriteLine("線程b:" + counter.Value); var token2 = counter.Add(); //計數器不變 當前計數器=2 Console.WriteLine("線程b:" + counter.Value); var token3 = counter.Add(); //計數器不變 當前計數器=2 Console.WriteLine("線程b:" + counter.Value); counter.Remove(token3); //計數器不變 當前計數器=2 Console.WriteLine("線程b:" + counter.Value); counter.Remove(token2); //計數器不變 當前計數器=2 Console.WriteLine("線程b:" + counter.Value); counter.Remove(token1); //計數器-1 當前計數器=1 Console.WriteLine("線程b:" + counter.Value); }).Start(); Console.ReadLine();
多線程模式測試結果
單線程模式測試結果