大话异步与并行(三)

上两章主要熟悉及验证异步与并行的基础知识,本节主要讲讲,现实中的需求--线程或异步给我们计算机带来的“性能”提升

我们最熟悉的不过就是操作数据作了,现以有两个数据库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();

        }
复制代码

 

小结:线程与并发应用领域很广,我们常见的就诸如数据库操作,而时常用多线程或异步来操作,性能更佳,但是不得不关心事务相关问题,线程间的事务一致性传播也是特别注意的!

posted on 2016-09-21 17:19  張暁磊  阅读(151)  评论(0编辑  收藏  举报