无序批量作业c#实现——并行计算与多线程的思考
最近一个项目中,客户单位原业务的扫描件是以文件形式存放在服务器硬盘固定目录下,而我们对扫描件的管理方式是将图片序列化成二进制存储在SQLServer数据库中(不要再说图片存在数据库中的不合理性,各取所需,权衡利弊),项目需要写个小程序,将160万张扫描件(大约400G)迁移到SQLServer数据库中。
由于需要判断图片是否已经导入、要件名称要通过数据库联查并将名称替换成标准的要件名,所以整个过程中与数据库的交互非常多,而且每一个要件的上传过程都要与数据库进行最少三次的交互(判断是否已导入、获取标准的汉字要件名称、写数据),最开始使用顺序的方式执行循环(c#常规的for),按照笔记本电脑(CPU:Intel i5 3410四核心,内存:12G)的电脑测算,160万张,导完需要9天!!岂能忍,这不搞笑吗!!
尝试改用多线程方式,毕竟导入图片都是同样的方式,一直执行而已,采用线程池,将每一个任务放到线程池中等待,使用ManualResetEvent设置阻塞并控制线程是否执行完结,通过ManualResetEvent的状态来处理UI端的等待提示的显示和隐藏。
foreach (PathInfo pathInfo in oldPathInfoConfig.PathInfos)
{
ThreadPool.QueueUserWorkItem(HandingPicture, pathInfo);
}
eventX.WaitOne(Timeout.Infinite, true);
本地运行都没啥事,效率还不错,提高了基本上5倍,但是放到服务器上,运行一会就报错,主要原因是数据库链接池不够用了,晕,手动设置下线程池的最大线程吧,给他设置成最大20个线程,按道理应该不会超过链接池的最大值了吧,郁闷的是,还是报同样或类似的错误,费解!改主意的也都注意了啊,connection和command都改成using的方式了,应该不存在释放不及时的情况。按道理线程池会自动考虑任务的情况而确定开启的线程数,但是毕竟这个多线程是有频繁的数据库交互,双方的门槛并不一致。
无奈之下,选择了并行的方式,并行其实也是多线程的一种,并行计算充分利用CPU的内核,执行也是无序的,但没有遇到报SQLServer连接池超限的情况了,而且效率还可以,测算之后,160万张图片,大约4小时能完成上传。
Parallel.ForEach(oldPathInfoConfig.PathInfos, HandingPicture);
HandingPicture完整代码
private void HandingPicture(object pathinfo)
{
PathInfo pathInfo = (PathInfo)pathinfo;
string sSelectSQL = "";
string sExistsSQL = "";
string sLog = "";
DataTable dt = new DataTable();
int index = pathInfo.LastName.LastIndexOf(".") + 1;
string extension = pathInfo.LastName.Substring(index, pathInfo.LastName.Length - index).ToUpper();
if (extension == "JPG" || extension == "BMP" || extension == "GIF" || extension == "PNG")
{
string slbh = "";
string ywid = "";
string yjmc = "";
int XH = 0;
string djid = "";
string sjid = "";
string bz = "";
int filescnt = 0;
string filename = pathInfo.LastName.Substring(0, index - 1);
string[] sName = filename.Split('_');
if (sName.Length == 2)//bz_xh
{
bz = sName[0];
XH = int.Parse(sName[1]);
sSelectSQL = string.Format("SELECT SLBH,YWID,YJMC,djid,sjid,bz FROM FC_SMJGJ where bz='{0}'", bz);
}
else if (sName.Length == 3)//djid_sjid_xh
{
djid = sName[0];
sjid = sName[1];
XH = int.Parse(sName[2]);
sSelectSQL = string.Format("SELECT SLBH,YWID,YJMC,djid,sjid,bz FROM FC_SMJGJ where djid='{0}' and sjid='{1}' ", djid, sjid);
}
if (!string.IsNullOrEmpty(sSelectSQL))
{
try
{
dt = DataAcess.GetDataTable(docConnectionString, sSelectSQL);
if (dt != null && dt.Rows.Count != 0)
{
slbh = dt.Rows[0]["SLBH"].ToString();
ywid = dt.Rows[0]["YWID"].ToString();
yjmc = dt.Rows[0]["YJMC"].ToString();
//判断是否已经导入过了
sExistsSQL = string.Format("SELECT COUNT(1) FROM Files where SLBH='{0}' AND YWID='{1}' AND YJMC='{2}' AND XH='{3}' ",
slbh, ywid, yjmc + "_" + XH.ToString(), XH);
filescnt = (int)DataAcess.ExecuteSelectedScalar(docConnectionString, sExistsSQL);
if (filescnt == 0)
{
sLog = "正在导入 " + pathInfo.Path + ";\r\n";
sAllLog += sLog;
//Action act = () => { txtLog.Text += sLog; };
//txtLog.Dispatcher.Invoke(act);
System.IO.FileStream fs = new System.IO.FileStream(pathInfo.Path, System.IO.FileMode.Open);
System.IO.BinaryReader br = new System.IO.BinaryReader(fs);
byte[] buffer = br.ReadBytes((int)fs.Length);
using (SqlConnection connection = new SqlConnection(fileConnectionString))
{
using (SqlCommand command =connection.CreateCommand())
{
if (connection.State != ConnectionState.Open)
connection.Open();
command.CommandTimeout = 600;
command.CommandText = "INSERT INTO Files(SLBH,YWID,YJMC,FileBody,XH,isUpload) VALUES (@SLBH,@YWID,@YJMC,@FileBody,@XH,@isUpload)";
command.Parameters.Add("@SLBH", SqlDbType.VarChar, 50).Value = slbh;
command.Parameters.Add("@YWID", SqlDbType.VarChar, 60).Value = ywid;
command.Parameters.Add("@YJMC", SqlDbType.VarChar, 50).Value = yjmc + "_" + XH.ToString();
command.Parameters.Add("@FileBody", SqlDbType.Binary, buffer.Length).Value = buffer;
command.Parameters.Add("@XH", SqlDbType.Int).Value = XH;
command.Parameters.Add("@isUpload", SqlDbType.Int).Value = 0;//0为未加密,1为已加密,设置为0
command.ExecuteNonQuery();
}
}
br.Close();
fs.Close();
}
}
}
catch (Exception ex)
{
sAllLog += ex.Message + ";\r\n";
}
}
}
}