這一篇將在以前的代碼生成器上講解多線程的應用,多線程的概念和好處這裏就很少說了,另外從本篇開始後面的實例代碼都將放到淘寶的SVN管理工具上維護,你們能夠直接使用SVN工具進行下載。好了下面進入本篇內容。html
閱讀目錄數據庫
這裏先講一下線程在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
咱們來看下執行時間,差很少用了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())); } } } }
代碼思路,判斷要生成的實體數量和Number10的關係,而後計算所需的線程數。ide
關鍵的一點是 thread.Join(),這段是主線程等待每一個線程執行完成。如今再來看下執行時間,差很少用了13秒,節省了將近10S的時間。svn
下面來考慮這樣的一個場景,在生成了文件的時候立刻在列表中提示實體生成完成,即進度提示的功能。咱們來看下winform中的兩種實現方式。工具
1.利用委託實現學習
先看一下普通線程實現方式,執行的時候會拋出以下異常。ui
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); }
先看一下msdn的介紹:this
訪問 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(); } } }
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(); } } }
1.操做步驟很簡單,從組件裏面拖一個BackgroundWorker組件設置WorkerReportsProgress(是否容許通知進度改變)爲true
2.添加DoWork(進行耗時操做) 和 ProgressChanged(進度改變執行) 方法
在寫數據字典生成工具以前本身對線程的使用仍是很模糊的,翻了不少資料和博客才學習到這些知識。若是您感受本文不錯,對您有所幫助,請您不吝點擊下右邊的推薦按鈕,謝謝!
本章代碼示例代碼下載地址:http://code.taobao.org/svn/DataDic_QuickCode ,請使用SVN進行下載!
目前總共有通過了七個版本的升級,如今提供最新版本的下載地址
數據字典生成工具V2.0安裝程序 | 最新安裝程序 | |
數據字典生成工具源代碼 | 最新源代碼 | |
http://code.taobao.org/svn/DataDicPub | SVN最新源碼共享地址 |
若是你使用了該工具,或者想學習該工具,歡迎加入這個小組,一塊兒討論數據字典生成工具、把該工具作的更強,更方便使用,一塊兒加入147425783 QQ羣。
更多數據字典生成工具資料請點擊數據字典生成工具專題。