• 大话异步与并行(三)


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

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

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

  • 相关阅读:
    KNN分类算法补充
    KNN分类算法实现手写数字识别
    KNN分类算法及python代码实现
    数据挖掘与机器学习介绍
    安装Numpy方法
    windows下的python环境搭建(python2和python3不兼容,python2用的多)
    用户画像知识
    Mahout介绍和简单应用
    协同过滤的实现步骤
    推荐系统基础知识
  • 原文地址:https://www.cnblogs.com/zhangxiaolei521/p/5893442.html
Copyright © 2020-2023  润新知