zoukankan      html  css  js  c++  java
  • 数据字典生成工具之旅(9):多线程使用及介绍

          这一篇将在之前的代码生成器上讲解多线程的应用,多线程的概念和好处这里就不多说了,另外从本篇开始后面的实例代码都将放到淘宝的SVN管理工具上维护,大家可以直接使用SVN工具进行下载。好了下面进入本篇内容。

    阅读目录

    线程的应用

         这里先讲一下线程在Web程序中的一个应用,之前的那一版代码生成器没有考虑表数量多的情形,这里先模拟一下在数据库中创建300张表的情形,下面给出创建表的语句 。

    --模拟创建300张表,@IsDropTable=0 表示创建表 IsDropTable=1 表示删除创建的模拟表
    DECLARE @IsDropTable AS BIT
    DECLARE @total AS INT 
    DECLARE @i AS INT
    SELECT @i=1,@total=300,@IsDropTable=0
    WHILE @i<=@total
    BEGIN
    DECLARE @strSQL AS VARCHAR(1000)
        --创建表
        SELECT @strSQL='
            CREATE TABLE myTest'+CONVERT(VARCHAR,@i)+'
            (
                [UserGUID] [uniqueidentifier] NOT NULL
            )
            EXEC sp_addextendedproperty N''MS_Description'', N''用户表'', ''SCHEMA'', N''dbo'', ''TABLE'', N''myTest'+CONVERT(VARCHAR,@i)+''', NULL, NULL
            EXEC sp_addextendedproperty N''MS_Description'', N''用户GUID'', ''SCHEMA'', N''dbo'', ''TABLE'', N''myTest'+CONVERT(VARCHAR,@i)+''', ''COLUMN'', N''UserGUID''
        '    
        IF @IsDropTable=1
        BEGIN
            --删除表
            SELECT @strSQL='DROP TABLE myTest'+CONVERT(VARCHAR,@i)
        END
        
        EXEC(@strSQL)
        SELECT @i=@i+1
    END
    View Code

        我们来看下执行时间,差不多用了22秒,时间还是挺长的。可以将代码改造一下,使用多线程来生成代码。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Web;
    using System.Configuration;
    using System.Collections;
    using System.IO;
    using System.Data;
    using System.Threading;
    
    namespace Mysoft.Code.Services
    {
        /// <summary>
        /// 代码生成类
        /// </summary>
        public class CodeGenerator
        {
            //模版文件路径
            private static string tmpPath = HttpContext.Current.Server.MapPath("/实体模版/Entity.vm");
    
            //模版输出路径
            private static string outPutPath = HttpContext.Current.Server.MapPath(ConfigurationManager.AppSettings["outputPath"]);
    
    
            private static readonly int Number10 = 400;
            private static readonly int MaxThreadCount = 4;
    
            /// <summary>
            /// 批量生成代码
            /// </summary>
            /// <param name="args">模版文件参数</param>
            public static void BatchGenerator(List<Hashtable> args)
            {
                if (!Directory.Exists(outPutPath))
                {
                    Directory.CreateDirectory(outPutPath);
                }
                //生成文件数量<Number10,则不开启线程生成
                if (args.Count < Number10)
                {
                    DoWork(args);
                }
                else
                {
                    //计算需要的线程数
                    int threadCount = args.Count % Number10 == 0 ? args.Count / Number10 : args.Count / Number10 + 1;
                    if (threadCount > MaxThreadCount)
                    {
                        threadCount = MaxThreadCount;
                    }
    
                    //每个线程需要生成的实体数量
                    int threadPqgeSize = (args.Count / threadCount) + 1;
                    int total = 0;
                    //为每个线程准备参数
                    List<List<Hashtable>> threadParams = new List<List<Hashtable>>();
                    for (int i = 0; i < threadCount; i++)
                    {
                        threadParams.Add(args.Skip(total).Take(threadPqgeSize).ToList());
                        total += threadParams[i].Count;
                    }
                    //创建线程
                    List<Thread> threads = new List<Thread>();
                    for (int i = 1; i < threadCount; i++)
                    {
                        Thread thread = new Thread(DoWork);
                        thread.IsBackground = true;
                        thread.Name = "CodeGenerator #" + i.ToString();
                        threads.Add(thread);
                        thread.Start(threadParams[i]);
                    }
    
                    // 为当前线程指派生成任务。
                    DoWork(threadParams[0]);
    
    
                    // 等待所有的编译线程执行线束。
                    foreach (Thread thread in threads)
                    {
                        thread.Join();
                    }
                }
            }
    
            private static void DoWork(Object listArgs)
            {
                List<Hashtable> list = (List<Hashtable>)listArgs;
                foreach (Hashtable ht in list)
                {
                    FileGen.GetFile(tmpPath, ht, string.Format("{0}\{1}.cs", outPutPath, ((DataTable)ht["T"]).Rows[0]["table_name"].ToString()));
                }
            }
    
        }
    }
    View Code

        代码思路,判断要生成的实体数量和Number10的关系,然后计算所需的线程数。

       关键的一点是 thread.Join(),这段是主线程等待每个线程执行完成。现在再来看下执行时间,差不多用了13秒,节省了将近10S的时间。

     

    winform程序中的多线程

        下面来考虑这样的一个场景,在生成了文件的时候马上在列表中提示实体生成完成,即进度提示的功能。我们来看下winform中的两种实现方式。

        1.利用委托实现

         先看一下普通线程实现方式,执行的时候会抛出如下异常。

       foreach (var key in query)
                {
                    dv.RowFilter = "tableid=" + key.tableid;
                    DataTable dtTable = dv.ToTable();
                    Hashtable ht = new Hashtable();
                    ht["T"] = dtTable;
                    string tableName = dtTable.Rows[0]["table_name"].ToString();
                    FileGen.GetFile(tmpPath, ht, string.Format("{0}\{1}.cs", outPutPath, tableName));
                    Thread thread = new Thread(BindMessage);
                    thread.IsBackground = true;
                    thread.Name = "BindMessage:" + key.tableid;
                    thread.Start(tableName);
                }
    View Code

         先看一下msdn的介绍:

           访问 Windows 窗体控件本质上不是线程安全的。如果有两个或多个线程操作某一控件的状态,则可能会迫使该控件进入一种不一致的状态。还可能出现其他与线程相关的 bug,包括争用情况和死锁。确保以线程安全方式访问控件非常重要。

      C#中禁止跨线程直接访问控件,InvokeRequired是为了解决这个问题而产生的,当一个控件的InvokeRequired属性值为真时,说明有一个创建它以外的线程想访问它。

          于是改变了思路,新建线程用以执行耗时的生成代码操作,在每生成一个实体时,通知UI线程更新dataGridView,达到实时更新的效果,这样主线程也不会阻塞了。

      

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;
    using Mysoft.Map.Extensions.DAL;
    using System.Collections;
    using Mysoft.Code.Services;
    using System.IO;
    using System.Threading;
    
    namespace ThreadWin
    {
        public partial class MainForm : Form
        {
            //模版文件路径
            private static string tmpPath = AppDomain.CurrentDomain.BaseDirectory + @"实体模版Entity.vm";
    
            //模版输出路径
            private static string outPutPath = AppDomain.CurrentDomain.BaseDirectory + @"模版输出路径";
    
            private DataTable dtInfo = new DataTable();
    
            /// <summary>
            /// 消息发送请求委托
            /// </summary>
            /// <param name="Msg">消息</param>
            delegate void SetMessageCallBack(object Msg);
    
            public MainForm()
            {
                InitializeComponent();
                dtInfo.Columns.Add("TableName");
                dtInfo.Columns.Add("Info");
                dtInfo.Columns.Add("Time");
                Control.CheckForIllegalCrossThreadCalls = false;
            }
    
            private void btn_OK_Click(object sender, EventArgs e)
            {
                dtInfo.Clear();
                
    
                if (!Directory.Exists(outPutPath))
                {
                    Directory.CreateDirectory(outPutPath);
                }
                //1.耗时的操作放在新建线程里面执行
                Thread thread = new Thread(GeneratorFile);
                thread.IsBackground = true;
                thread.Start();
    
                //2.使用系统的线程池进行线程操作
                //ThreadPool.QueueUserWorkItem(GeneratorFile);
            }
    
            /// <summary>
            /// 这里进行耗时的生成代码操作
            /// </summary>
            private void GeneratorFile()
            {
                //循环生成实体,并且在列表上显示进度
                DataTable dt = GetAllTableInfo();
                DataView dv = dt.DefaultView;
                var query = (from p in dt.AsEnumerable()
                             group p by new { TableId = p.Field<int>("tableid"), TableName = p.Field<string>("table_name") } into q
                             select new { TableId = q.Key.TableId, TableName = q.Key.TableName }
                            );
                foreach (var key in query)
                {
                    dv.RowFilter = "tableid=" + key.TableId;
                    DataTable dtTable = dv.ToTable();
                    Hashtable ht = new Hashtable();
                    ht["T"] = dtTable;
                    string tableName = dtTable.Rows[0]["table_name"].ToString();
                    FileGen.GetFile(tmpPath, ht, string.Format("{0}\{1}.cs", outPutPath, key.TableName));
    
                    //消息提示
                    DataRow dr = dtInfo.NewRow();
                    dr["TableName"] = tableName;
                    dr["Info"] = "生成成功";
                    dr["Time"] = DateTime.Now.ToString();
                    dtInfo.Rows.Add(dr);
                    DataView dvOrder = dtInfo.DefaultView;
                    dvOrder.Sort = "Time DESC";
                    DataTable dtinfo = dvOrder.ToTable();
                    if (this.dataGridView.InvokeRequired)
                    {
                        SetMessageCallBack stms = new SetMessageCallBack(BindMessage);
                        if (this != null)
                        {
                            this.Invoke(stms, new object[] { dtinfo });
                        }
                    }
                    else
                    {
                        dataGridView.DataSource = dvOrder.ToTable();
                    }
                }
    
                
            }
    
            /// <summary>
            /// 列表显示最新消息
            /// </summary>
            /// <param name="dt"></param>
            private void BindMessage(object dt)
            {
                dataGridView.DataSource = dt;
            }
    
            /// <summary>
            /// 获取所有表信息
            /// </summary>
            /// <returns></returns>
            public DataTable GetAllTableInfo()
            {
                string strSQL = @"    SELECT T.name AS table_name ,
                T.object_id AS tableid,
                ISNULL(CONVERT(VARCHAR(MAX), E.value), '') AS table_name_c ,
                C.name AS field_name ,
                ISNULL(CONVERT(VARCHAR(MAX), D.value), '') AS field_name_c ,
                ROW_NUMBER() OVER(PARTITION BY T.name ORDER BY C.colid) AS field_sequence ,
                TYPE_NAME(C.xtype) AS date_type ,
                (CASE WHEN EXISTS ( SELECT   1
                FROM     sysobjects
                WHERE    xtype = 'PK'
                AND name IN (
                SELECT  name
                FROM    sysindexes
                WHERE   id = C.id
                AND indid IN (
                SELECT  indid
                FROM    sysindexkeys
                WHERE   id = C.id
                AND colid = C.colid ) ) )
                THEN 1
                ELSE 0
                END) AS pk ,
                ISNULL(C.isnullable, 1) AS isnullable ,
                ISNULL(COLUMNPROPERTY(c.id, c.name, 'IsIdentity'), 0) AS isidentity
                FROM   sys.tables AS T
                LEFT JOIN syscolumns AS C ON c.id = T.object_id
                LEFT JOIN sys.extended_properties AS D ON D.major_id = T.object_id
                AND D.minor_id = C.colid
                AND D.major_id = C.id
                LEFT JOIN sys.extended_properties AS E ON E.major_id = T.object_id
                AND E.minor_id = 0
            WHERE T.object_id IN (SELECT object_id FROM sys.Tables)";
    
                return CPQuery.From(strSQL).FillDataTable();
            }
        }
    }
    View Code

        2.BackgroundWorker

         除了自己使用Thread或者ThreadPool来实现跨线程更新UI还可以使用BackgroundWorker组件来实现该效果。

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;
    using Mysoft.Map.Extensions.DAL;
    using System.Collections;
    using Mysoft.Code.Services;
    using System.IO;
    using System.Threading;
    
    namespace ThreadWin
    {
        public partial class BackgroundWorkerForm : Form
        {
            //模版文件路径
            private static string tmpPath = AppDomain.CurrentDomain.BaseDirectory + @"实体模版Entity.vm";
    
            //模版输出路径
            private static string outPutPath = AppDomain.CurrentDomain.BaseDirectory + @"模版输出路径";
    
            private DataTable dtInfo = new DataTable();
    
            /// <summary>
            /// 消息发送请求委托
            /// </summary>
            /// <param name="Msg">消息</param>
            delegate void SetMessageCallBack(object Msg);
    
            public BackgroundWorkerForm()
            {
                InitializeComponent();
                dtInfo.Columns.Add("TableName");
                dtInfo.Columns.Add("Info");
                dtInfo.Columns.Add("Time");
                Control.CheckForIllegalCrossThreadCalls = false;
            }
    
            private void btn_OK_Click(object sender, EventArgs e)
            {
                dtInfo.Clear();
                
    
                if (!Directory.Exists(outPutPath))
                {
                    Directory.CreateDirectory(outPutPath);
                }
    
                //判断线程是否Busy
                if (mBackgroundWorker.IsBusy)
                {
                    MessageBox.Show("当前进程正在生成代码,请等待本次操作完成!");
                    return;
                }
                mBackgroundWorker.RunWorkerAsync();
            }
    
            private void mBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
            {
                BackgroundWorker bw = (BackgroundWorker)sender;
    
                //循环生成实体,并且在列表上显示进度
                DataTable dt = GetAllTableInfo();
                DataView dv = dt.DefaultView;
                var query = (from p in dt.AsEnumerable()
                             group p by new { TableId = p.Field<int>("tableid"), TableName = p.Field<string>("table_name") } into q
                             select new { TableId = q.Key.TableId, TableName = q.Key.TableName }
                            );
                foreach (var key in query)
                {
                    dv.RowFilter = "tableid=" + key.TableId;
                    DataTable dtTable = dv.ToTable();
                    Hashtable ht = new Hashtable();
                    ht["T"] = dtTable;
                    string tableName = dtTable.Rows[0]["table_name"].ToString();
                    FileGen.GetFile(tmpPath, ht, string.Format("{0}\{1}.cs", outPutPath, key.TableName));
    
                    //消息提示
                    DataRow dr = dtInfo.NewRow();
                    dr["TableName"] = tableName;
                    dr["Info"] = "生成成功";
                    dr["Time"] = DateTime.Now.ToString();
                    dtInfo.Rows.Add(dr);
                    DataView dvOrder = dtInfo.DefaultView;
                    dvOrder.Sort = "Time DESC";
                    DataTable dtinfo = dvOrder.ToTable();
                    //通知进度改变
                    bw.ReportProgress(0, dtinfo);
                } 
            }
    
            /// <summary>
            /// 进度改变执行
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void mBackgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
            {
                dataGridView.DataSource = e.UserState;
            }
    
            /// <summary>
            /// 获取所有表信息
            /// </summary>
            /// <returns></returns>
            public DataTable GetAllTableInfo()
            {
                string strSQL = @"    SELECT T.name AS table_name ,
                T.object_id AS tableid,
                ISNULL(CONVERT(VARCHAR(MAX), E.value), '') AS table_name_c ,
                C.name AS field_name ,
                ISNULL(CONVERT(VARCHAR(MAX), D.value), '') AS field_name_c ,
                ROW_NUMBER() OVER(PARTITION BY T.name ORDER BY C.colid) AS field_sequence ,
                TYPE_NAME(C.xtype) AS date_type ,
                (CASE WHEN EXISTS ( SELECT   1
                FROM     sysobjects
                WHERE    xtype = 'PK'
                AND name IN (
                SELECT  name
                FROM    sysindexes
                WHERE   id = C.id
                AND indid IN (
                SELECT  indid
                FROM    sysindexkeys
                WHERE   id = C.id
                AND colid = C.colid ) ) )
                THEN 1
                ELSE 0
                END) AS pk ,
                ISNULL(C.isnullable, 1) AS isnullable ,
                ISNULL(COLUMNPROPERTY(c.id, c.name, 'IsIdentity'), 0) AS isidentity
                FROM   sys.tables AS T
                LEFT JOIN syscolumns AS C ON c.id = T.object_id
                LEFT JOIN sys.extended_properties AS D ON D.major_id = T.object_id
                AND D.minor_id = C.colid
                AND D.major_id = C.id
                LEFT JOIN sys.extended_properties AS E ON E.major_id = T.object_id
                AND E.minor_id = 0
            WHERE T.object_id IN (SELECT object_id FROM sys.Tables)";
    
                return CPQuery.From(strSQL).FillDataTable();
            }
        }
    }
    View Code

        1.操作步骤很简单,从组件里面拖一个BackgroundWorker组件设置WorkerReportsProgress(是否允许通知进度改变)为true

        2.添加DoWork(进行耗时操作) 和 ProgressChanged(进度改变执行) 方法

    本章总结

         在写数据字典生成工具之前自己对线程的使用还是很模糊的,翻了很多资料和博客才学习到这些知识。如果您感觉本文不错,对您有所帮助,请您不吝点击下右边的推荐按钮,谢谢!

         本章代码示例代码下载地址:http://code.taobao.org/svn/DataDic_QuickCode ,请使用SVN进行下载!

    工具源代码下载

          目前总共有经过了七个版本的升级,现在提供最新版本的下载地址

    学习使用

          如果你使用了该工具,或者想学习该工具,欢迎加入这个小组,一起讨论数据字典生成工具、把该工具做的更强,更方便使用,一起加入147425783 QQ群

          更多数据字典生成工具资料请点击数据字典生成工具专题

  • 相关阅读:
    LiteFlow 按照规则配置进行复杂流转
    ImageCombiner 服务端合图
    forest HTTP调用API框架
    smart-doc API文档生成工具
    YAML语法和用法
    拓展mybatisPlus 支持批量插入
    ModbusRTU控制SV660P说明
    .NET RulesEngine(规则引擎)
    Win10自动更新有效强制永久关闭
    Redis 到底是怎么实现“附近的人”这个功能的?
  • 原文地址:https://www.cnblogs.com/yanweidie/p/4374913.html
Copyright © 2011-2022 走看看