大话异步与并行(三)
上两章主要熟悉及验证异步与并行的基础知识,本节主要讲讲,现实中的需求--线程或异步给我们计算机带来的“性能”提升
我们最熟悉的不过就是操作数据作了,现以有两个数据库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(); }
小结:线程与并发应用领域很广,我们常见的就诸如数据库操作,而时常用多线程或异步来操作,性能更佳,但是不得不关心事务相关问题,线程间的事务一致性传播也是特别注意的!