上兩章主要熟悉及驗證異步與並行的基礎知識,本節主要講講,現實中的需求--線程或異步給我們計算機帶來的“性能”提升
我們最熟悉的不過就是操作數據作了,現以有兩個數據庫AccountA和AccountB,為了模擬,里面分別有相同的user表。
同步方式就是針對兩張表登錄事務然后事務提交insert ,就如上圖所示,對針數據庫是一條一條insert只是加入了事務處理
如果哪一條失敗,將會回滾。這個很簡單,看下面的例子
static void Main(string[] args) { DateTime now = DateTime.Now; SqlConnection conn1 = new SqlConnection("data source=.;Initial Catalog=AccountA;Integrated Security=SSPI"); SqlConnection conn2 = new SqlConnection("data source=.;Initial Catalog=AccountB;Integrated Security=SSPI"); CommittableTransaction ct = new CommittableTransaction(); conn1.Open(); conn1.EnlistTransaction(ct); conn2.Open(); conn2.EnlistTransaction(ct); try { SqlCommand command1 = new SqlCommand("INSERT INTO [AccountA].[dbo].[user]([UserName])VALUES(111)", conn1); command1.ExecuteNonQuery(); SqlCommand command2 = new SqlCommand("INSERT INTO [AccountB].[dbo].[user]([UserName])VALUES(111)", conn2); command2.ExecuteNonQuery();
ct.Commit(); } catch (Exception err) { Console.WriteLine(err); ct.Rollback(); } finally { conn1.Close(); conn2.Close(); } Console.WriteLine("running time: " + (DateTime.Now - now).TotalSeconds); Console.ReadKey(); }
在這里,為了觀察每次insert的時間,一共運行了六次,平均時間在2.2s左右。
上面的方法利用CommittableTransaction顯示聲明事務,然后為每個Sqlconnection 連接登記 SqlConnection.EnlistTransaction,然后通過事務提交,失敗將會事務回滾!
其實對於顯示事務,可以更改當前事務環境,這歸功於Transaction.Current,Current是Transaction的可讀寫的屬於,當為它賦值操作,即改變了當前的事務環境,不用再對每個Sqlconnection 連接登記 SqlConnection.EnlistTransaction。
如下面代碼所示
static void Main(string[] args) { DateTime now = DateTime.Now; SqlConnection conn1 = new SqlConnection("data source=.;Initial Catalog=AccountA;Integrated Security=SSPI"); SqlConnection conn2 = new SqlConnection("data source=.;Initial Catalog=AccountB;Integrated Security=SSPI"); CommittableTransaction ct = new CommittableTransaction(); Transaction oTransaction = Transaction.Current; Transaction.Current = ct; conn1.Open(); conn2.Open(); try { SqlCommand command1 = new SqlCommand("INSERT INTO [AccountA].[dbo].[user]([UserName])VALUES('001')", conn1); command1.ExecuteNonQuery(); SqlCommand command2 = new SqlCommand("INSERT INTO [AccountB].[dbo].[user]([UserName])VALUES('001')", conn2); command2.ExecuteNonQuery(); ct.Commit(); } catch (Exception err) { Console.WriteLine(err); Transaction.Current = oTransaction; ct.Rollback(); } finally { conn1.Close(); conn2.Close(); } Console.WriteLine("running time: " + (DateTime.Now - now).TotalSeconds); Console.ReadKey(); }
當然,本文不是重點講事務,如有興趣請參考事務相關的文章,這里只作簡單的介紹。
話題回來,那么,如何把同步的事務機制利用線程或異步來執行呢?
通過上圖所示,我們應該要創建兩個線程並行處理操作對象(這里是數據庫)。
現在有兩個難點,一事務必須跨線程,二事務必須保持一致。如果對事務不太清楚的同學,不用太着急,以后,將專門章節來闡述事務。
按照上面的思路代碼也不會太難了。
static void Main(string[] args) { DateTime now = DateTime.Now; SqlConnection conn1 = new SqlConnection("data source=.;Initial Catalog=AccountA;Integrated Security=SSPI"); SqlConnection conn2 = new SqlConnection("data source=.;Initial Catalog=AccountB;Integrated Security=SSPI"); CommittableTransaction ct = new CommittableTransaction(); Transaction oTransaction = Transaction.Current; Transaction.Current = ct; try { new Thread((c) => { try { conn1.Open(); SqlCommand command1 = new SqlCommand("INSERT INTO [AccountA].[dbo].[user]([UserName])VALUES('006')", conn1); command1.ExecuteNonQuery(); var ct1 = c as DependentTransaction; ct1.Complete(); } catch (ThreadStateException ex) { } catch (Exception ex) { } }).Start(ct.DependentClone(DependentCloneOption.BlockCommitUntilComplete)); new Thread((c) => { try { conn2.Open(); SqlCommand command2 = new SqlCommand("INSERT INTO [AccountB].[dbo].[user]([UserName])VALUES('006')", conn2); command2.ExecuteNonQuery(); var ct2 = c as DependentTransaction; ct2.Complete(); } catch (ThreadStateException ex) { } catch (Exception ex) { } }).Start(ct.DependentClone(DependentCloneOption.BlockCommitUntilComplete)); ct.Commit(); } catch (Exception err) { Console.WriteLine(err); ct.Rollback(); } finally {
Transaction.Current = oTransaction; conn1.Close(); conn2.Close(); } Console.WriteLine("running time: " + (DateTime.Now - now).TotalSeconds); Console.ReadKey(); }
這次的時間,因為是異步的,得出的時間明顯減少,注意,這個時間不是兩個線程跑的時間,因為主線程main沒有等待它們,直接進行下去了。
初步統計了兩個線程時間並行時間是0.8s左右大於上次測試2.2s,這就是充分說明了,並行線程開發大大提高了時間效率。
細心的同學會看到,上面通過 Transaction.DependentClone Method (DependentCloneOption) 事務依懶,給各個線程,然后通過Thread.Start()給線程傳值,不清楚的可以參考 大話異步與並行(一),這里不再累贅!
當然,因為在這里,只是並行兩個線程,主線程本身就是一個線程(實際上只要再開一個新線程),就像下面的代碼所示:
static void Main(string[] args) { DateTime now = DateTime.Now; SqlConnection conn1 = new SqlConnection("data source=.;Initial Catalog=AccountA;Integrated Security=SSPI"); SqlConnection conn2 = new SqlConnection("data source=.;Initial Catalog=AccountB;Integrated Security=SSPI"); CommittableTransaction ct = new CommittableTransaction(); Transaction oTransaction = Transaction.Current; Transaction.Current = ct; try { conn1.Open(); SqlCommand command1 = new SqlCommand("INSERT INTO [AccountA].[dbo].[user]([UserName])VALUES('007')", conn1); command1.ExecuteNonQuery(); new Thread((c) => { try { conn2.Open(); SqlCommand command2 = new SqlCommand("INSERT INTO [AccountB].[dbo].[user]([UserName])VALUES('007')", conn2); command2.ExecuteNonQuery(); var ct2 = c as DependentTransaction; ct2.Complete(); } catch (ThreadStateException ex) { } catch (Exception ex) { } }).Start(ct.DependentClone(DependentCloneOption.BlockCommitUntilComplete)); ct.Commit(); } catch (Exception err) { Console.WriteLine(err); ct.Rollback(); } finally { Transaction.Current = oTransaction; conn1.Close(); conn2.Close(); } Console.WriteLine("running time: " + (DateTime.Now - now).TotalSeconds); Console.ReadKey(); }
同理,線程只是實現異步的一種方法,那么前兩節重復這個概念,利用異步委托,一樣可以實現線程,只不過,這時是后台線程,他們是利用線程池實現的,
線程池:就是微軟在線程的基礎上,封裝的另一套“組件”,因為,線程,在很多時間很操心,到處創建,創建時需要消耗一定時間,而ThreadPool線程池,就是在事先維護好的一些線程組合,可以理解成 List<Thread> ,當我們需要線程的時候,系統會為我們自動分配線程,這就不僅方便,而且快捷(時間)。
看看異步委托是如何實現跨線程事務的。
static void Main(string[] args) { DateTime now = DateTime.Now; SqlConnection conn1 = new SqlConnection("data source=.;Initial Catalog=AccountA;Integrated Security=SSPI"); SqlConnection conn2 = new SqlConnection("data source=.;Initial Catalog=AccountB;Integrated Security=SSPI"); CommittableTransaction ct = new CommittableTransaction(); Transaction oTransaction = Transaction.Current; Transaction.Current = ct; try { conn1.Open(); SqlCommand command1 = new SqlCommand("INSERT INTO [AccountA].[dbo].[user]([UserName])VALUES('009')", conn1); command1.ExecuteNonQuery(); Action<DependentTransaction> f = c => { try { conn2.Open(); SqlCommand command2 = new SqlCommand("INSERT INTO [AccountB].[dbo].[user]([User])VALUES('009')", conn2); command2.ExecuteNonQuery(); var ct2 = c as DependentTransaction; ct2.Complete(); } catch (ThreadStateException ex) { } catch (Exception ex) { } }; f.BeginInvoke(ct.DependentClone(DependentCloneOption.BlockCommitUntilComplete), r => { try { ct.Commit(); } catch (ThreadStateException ex) { } catch (Exception ex) { } }, null); } catch (Exception err) { Console.WriteLine(err); ct.Rollback(); } finally { Transaction.Current = oTransaction; conn1.Close(); conn2.Close(); } Console.WriteLine("running time: " + (DateTime.Now - now).TotalSeconds); Console.ReadKey(); }
認真看過前面兩節異步與並行文章的話,理解起來很順暢。
首先,我們知道,事務的傳遞或傳播靠的是委托Delegate.BeginInvoke(),里面傳入事務依懶對象 Transaction.DependentClone Method (DependentCloneOption) ,從而使從線程與主線程利用相同的事務。所謂依懶,就是主線程在外層提交事務Transaction.Complete()時,必須等待從線程事務是否已准備就緒。
在.net 2.0里還有另外一類 TransactionScope 隱式事務 ,我們前面說的CommittableTransaction 事務類它屬於顯式事務,所謂隱式事務,就是在區域環境(代碼塊)內自動賦於事務的能力或功能,顯式事務就是我們要手動的給所需事務對象顯示的賦值。
當然也有人在討論到底哪個好,哪個壞,這個要看需要。因為顯示在跨線程或函數傳播來的更直接!而隱式能讓我們省寫不少代碼,不僅如此,代碼也更加優美。
就如下所以,我們依然用異步委托來說明
static void Main(string[] args) { DateTime now = DateTime.Now; Action<DependentTransaction> f = c => { using (TransactionScope scope = new TransactionScope()) { using (SqlConnection conn = new SqlConnection("data source=.;Initial Catalog=AccountB;Integrated Security=SSPI")) { conn.Open(); SqlCommand command = new SqlCommand("INSERT INTO [AccountB].[dbo].[user]([UserName])VALUES('000')", conn); command.ExecuteNonQuery(); scope.Complete(); } } var ct = c as DependentTransaction; ct.Complete(); }; using (TransactionScope scope = new TransactionScope()) { using (SqlConnection conn = new SqlConnection("data source=.;Initial Catalog=AccountA;Integrated Security=SSPI")) { conn.Open(); SqlCommand command = new SqlCommand("INSERT INTO [AccountA].[dbo].[user]([User])VALUES('000')", conn); command.ExecuteNonQuery(); f.BeginInvoke(Transaction.Current.DependentClone(DependentCloneOption.BlockCommitUntilComplete), null, null); scope.Complete(); } } Console.WriteLine("running time: " + (DateTime.Now - now).TotalSeconds); Console.ReadKey(); }
小結:線程與並發應用領域很廣,我們常見的就諸如數據庫操作,而時常用多線程或異步來操作,性能更佳,但是不得不關心事務相關問題,線程間的事務一致性傳播也是特別注意的!
未完待續...