这一篇将在之前的代码生成器上讲解多线程的应用,多线程的概念和好处这里就不多说了,另外从本篇开始后面的实例代码都将放到SVN管理工具上维护,大家可以直接使用SVN工具进行下载。好了下面进入本篇内容。
阅读目录
- 线程的应用
- winform程序中的多线程
- 本章总结
- 工具源代码下载
- 学习使用
回到顶部
线程的应用
这里先讲一下线程在Web程序中的一个应用,之前的那一版代码生成器没有考虑表数量多的情形,这里先模拟一下在数据库中创建300张表的情形,下面给出创建表的语句 。
代码语言:javascript复制--模拟创建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秒,时间还是挺长的。可以将代码改造一下,使用多线程来生成代码。
代码语言:javascript复制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的关系,然后计算所需的线程数。
关键的一点是 thread.Join(),这段是主线程等待每个线程执行完成。现在再来看下执行时间,差不多用了13秒,节省了将近10S的时间。
回到顶部
winform程序中的多线程
下面来考虑这样的一个场景,在生成了文件的时候马上在列表中提示实体生成完成,即进度提示的功能。我们来看下winform中的两种实现方式。
1.利用委托实现
先看一下普通线程实现方式,执行的时候会抛出如下异常。
代码语言:javascript复制 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的介绍:
访问 Windows 窗体控件本质上不是线程安全的。如果有两个或多个线程操作某一控件的状态,则可能会迫使该控件进入一种不一致的状态。还可能出现其他与线程相关的 bug,包括争用情况和死锁。确保以线程安全方式访问控件非常重要。
C#中禁止跨线程直接访问控件,InvokeRequired是为了解决这个问题而产生的,当一个控件的InvokeRequired属性值为真时,说明有一个创建它以外的线程想访问它。
于是改变了思路,新建线程用以执行耗时的生成代码操作,在每生成一个实体时,通知UI线程更新dataGridView,达到实时更新的效果,这样主线程也不会阻塞了。
代码语言:javascript复制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组件来实现该效果。
代码语言:javascript复制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最新源码共享地址 |
回到顶部