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