多线程学习杂记
1,使用new Task或者Task.Factory.StartNew创建父线程与子线程时,如果没有在子线程创建的时候添加“TaskCreationOptions.AttachedToParent”参数,则两个线程没有依附关系。 只有子线程添加了“TaskCreationOptions.AttachedToParent”参数后,在父线程等待的时候,才会将子线程视做父线程的一部分,等子线程运行完毕再执行父线程中的代码。
Task task_Parent = Task.Factory.StartNew(()=>
{
Task task_Child = Task.Factory.StartNew(()=>
{
Thread.Sleep(5000);
Console.WriteLine("子线程执行完毕...")
})
Console.WriteLine("父线程执行完毕")
})
task_Parent.ContinueWith(t=>{Console.WriteLine("11111")})
//本质上还是两个彼此独立的线程,执行结果是
"父线程执行完毕"
"11111"
5秒后——"子线程执行完毕..."
Task task_Parent = Task.Factory.StartNew(()=>
{
//加上TaskCreationOptions.AttachedToParent参数
Task task_Child = Task.Factory.StartNew(()=>
{
Thread.Sleep(5000);
Console.WriteLine("子线程执行完毕...")
},TaskCreationOptions.AttachedToParent)
Console.WriteLine("父线程执行完毕")
})
task_Parent.ContinueWith(t=>{Console.WriteLine("11111")})
//"父线程执行完毕"
//5秒后——"子线程执行完毕..."
//"11111"
//父线程依旧会正常执行,但是在ContinueWith的时候,会把子线程视做父线程的一部分,等父线程中的子线程全部执行完,才会执行ContinueWith中的代码。
(如果父线程是需要拿到子线程中的返回值,则需要先等待子线程执行完才应该继续执行父线程中的代码。)
Task.Run无法添加“TaskCreationOptions”参数,因为其底层代码中含有“TaskCreationOptions.DenyChildAttach”。所以Task.Run创建的子线程无法与父线程形成依赖关系。
SQLServer学习杂记
创建一个自己的SQLHelper类,在类中使用command.ExecuteReader(CommandBehavior.CloseConnection)方法时,需要返回一个SQLDataReader类型,这个类型在类外(如Main方法中)调用中,因为需要读取器(reader)打开并遍历,读取器可能会在某个时刻被释放并自动关闭,但这并不保证立即发生,因此连接也不能立即关闭。所以要么在外部显式的使用reader.close()方法,要么就在using块中使用SQLHelper类里包含ExecuteReader的方法。(PS,因为是在外部打开并遍历reader读取器,所以在SQLHelper类内部也不能直接使用close()等方法)
SqlParameter类是数据库操作中的一部分,它可以用来向数据库传递参数化查询的参数,确保用户提供的信息正确的识别为参数而不是SQL代码。
为确保Sqlconnection与SQLCommand能正确释放,应该使用using语句块来声明这俩对象。
单例模式
单例模式有三种实现方式,不管哪一种,都需要将构造方法私有化,使其无法在外部new出实例。
饿汉模式:类内声明一个私有的、静态的、只读的字段,类型为该class,赋值为该类的new实例,再通过一个静态属性的get方法或者一个公开的静态方法返回这个实例。
class AAA
{
private AAA() { }
private static readonly AAA _instance = new AAA();
public static AAA GetInstance
{
get
{
return _aaa;
}
}
}
懒汉模式:类内先声明一个私有静态该类的字段,但是先不要赋值,在静态属性的get方法中,先判断字段是否为null,如果是null,再对字段new实例后,返回出去。
class BBB
{
private BBB() { }
private static BBB _instance = null;
public static BBB GetInstance
{
get
{
if (_instance == null)
{
_instance = new BBB();
}
return _instance;
}
}
}
使用懒汉模式还需要考虑到线程安全的问题,因为当单例还没有被实例化时的状态被多个线程同时取到,进而多个线程都会创建出实例,所以要使用lazy语法糖加锁,修改后:
class BBB
{
private BBB() { }
private static readonly Lazy<BBB> _instance = new Lazy<BBB>(() => new BBB());
public static BBB GetInstance
{
get
{
return _instance.Value;
}
}
}
饿汉模式与懒汉模式的区别,在于饿汉模式是程序执行前就已经把实例创建好了,会占用部分内存,但不会有线程问题;而懒汉模式因为添加的Lazy线程锁避免了线程问题,同时又保证了类在首次访问GetInstance时被创建。
内部静态类:在类中声明一个私有静态类classinner,在这个类中再公开一个静态只读的、类型为单例类的字段并new出实例,最后使用公开的静态属性中get方法返回静态类中的字段。
class CCC
{
private CCC() { }
private static class CCCInner
{
static CCCInner() { }
public static readonly CCC _instance = new CCC();
}
public static CCC GetInstance { get { return CCCInner._instance; } }
}
内部静态类实现方式巧妙利用了类加载机制和静态构造函数的特性,既保证了线程安全,又实现了延迟初始化,是一种高效且简洁的单例模式实现方式。
读取外部excel表
两种读取excel表的方法,一种是使用Microsoft.ACE.OLEDB组件,通过OLE DB接口访问Excel文件,在用法上,与连接SQL数据库没有太大差别
string excelPath = @"\path";
string connString = $"Provider=Microsoft.ACE.OLEDB.12.0;Data Source{excelFilePath};Extended Properties='Excel 12.0 Xml;HDR=YES;'"
//使用using确保资源正确释放
//创建OLE DB连接
using(OleDbConnection oconn = new OleDbConnection(connString)){
oconn.Open();
//定义SQL执行语句
string SQL = "SELECT * FROM [Sheet1$]";
//创建command对象
using(var cmd = new OleDbCommand(SQL,oconn)){
//创建读取器并执行查询
using(var adapter = new OleDbDataAdapter(cmd)){
//将读取器中的结果,填充到dataTable中
DataTable dt = new DataTable();
adapter.Fill(dt);
}
}
}
以上是处理单个独立的表的时候用到的方式,如果要处理多个相关联的表,则需要将读取器中的结果填充到DataSet中,DataSet 可以容纳多个 DataTable,并允许您通过 DataRelation 来定义和维护表间关系,便于进行数据的导航和关联查询。
另一种是如果没有Microsoft.ACE.OLEDB组件,可以通过nuget包里的 NPOI库来对外部的.xls或.xlsx表读取,
NPOI库中有两个类可以用来创建和代表Excel的类(HSSFWorkbook,XSSFWorkbook)前者用于.xls,后者用于.xlsx。
用ISheet接口(继承了接口的HSSFSheet/XSSFSheet)来选择要读取的工作表。
之后可以通过ISheet中提供的方法遍历excel表中的行和列,将结果填充到DataTable中。(在Excel中,第一行为列标题,用来创建DataTable的列结构,之后遍历其余的行,填充到DataTable中)
using(var workBook = new XSSFWorkBook(excelFilePath)){
//按数组下标选取读取的表
var sheet = workBool.GetSheetAt(0);
//声明新表来为填充做准备
DataTable dt = new DataTable();
//遍历所选表中的行
foreach(IRow row in sheet){
//通常第一行都是列标题
if(row.RowNum == 0){
for(i = 0,i<row.lastCellNum,i++){
//这句的意思:先获取单元格中的对象,如果不是null则转成文本,如果是null则返回null,最后??表示如果左侧不是null则返回,否则将null转成Empty返回。
string colName = row.GetCell(i)?.ToString()?? String.Empty;
//表中添加列与列名
dt.Columns.Add(colName);
}
}
else{
//第二行开始为需要的数据
//创建好行数据,为填充DataTable做准备
DataRow dr = new DataRow();
//循环读取行中的数据并填充到dr中
for(i = 0,i<row.lastCellNum,i++){
Object value = row.GetCell(i)?.ToString()?? String.Empty;
dr[i] = value;
}
//往表中添加行数据
dt.Rows.Add(dr);
}
}
}
OLEDB与NPOI库的优劣: 文言一心总结吧。
将DataTable导入数据库
有两种方式,
先假设数据库中有个StudentDB表,里面有学生的相关信息
class Student
{
public int StudentId { get; set; }
public string StudentName { get; set; }
public DateTime StuBrithday { get; set; }
public int PhontNum { get; set; }
}
以下为第一种方式
/// 通过SQL事务的方法把DataTable中的数据导入数据库
static void ImportSQLServerDB(DataTable dt)
{
//主是先将dt中的数据重新还原成一个个学生
var listStu = new List<Student>();
foreach (DataRow row in dt.Rows)
{
var stu = new Student()
{
StudentId = Convert.ToInt32(row["StudentId"]),
StudentName = row["StudentName"].ToString()!,
StuBrithday = Convert.ToDateTime(row["StuBrithday"]),
PhontNum = Convert.ToInt32(row["PhontNum"])
};
listStu.Add(stu);
}
var listSQL = new List<string>();
foreach (var stu in listStu)
{
string SQL = $"INNER INTO dbo.Students ([StudentId],[StudentName],[StuBrithday],[PhontNum]) VALUES ({stu.StudentId},'{stu.StudentName}','{stu.StuBrithday}',{stu.PhontNum})";
listSQL.Add(SQL);
}
//最后执行SQL事务...
SQLAddTransaction(listSQL);
}
static void SQLAddTransaction(List<string> listSQL)
{
string connString = "";
SqlConnection connection = new SqlConnection(connString);
SqlCommand cmd = connection.CreateCommand();
cmd.CommandType = CommandType.Text;
try
{
connection.Open();
cmd.Transaction = connection.BeginTransaction();
foreach (var sql in listSQL)
{
cmd.CommandText = sql;
cmd.ExecuteNonQuery();
}
cmd.Transaction.Commit();
}
catch (Exception ex)
{
cmd.Transaction?.Rollback();
throw new Exception(ex.Message);
}
finally
{
if (cmd.Transaction != null)
{
cmd.Transaction = null;
}
connection.Close();
}
}
以下是第二种方式:
/// 通过SQLBulkCopy类将DataTable导入数据库
static void ImportBulkCopy(DataTable sourceDt)
{
//首先需要确保DataTable中的数据类型与数据库中一致
//新建一个DataTable
DataTable copyDt = sourceDt.Clone();
//设定好指定列的数据类型
copyDt.Columns["StudentId"].DataType = typeof(int);
copyDt.Columns["PhontNum"].DataType = typeof(int);
copyDt.Columns["StuBrithday"].DataType = typeof(DateTime);
//将原DataTable中的数据转换后,复制到新建的表中
foreach (DataRow row in sourceDt.Rows)
{
DataRow newRow = copyDt.NewRow();
if (int.TryParse(row["StudentId"].ToString(), out int intStuId))
{
newRow["StudentId"] = intStuId;
}
if (int.TryParse(row["PhontNum"].ToString(), out int intPhone))
{
newRow["PhontNum"] = intPhone;
}
if (int.TryParse(row["StuBrithday"].ToString(), out int intBrithday))
{
newRow["StuBrithday"] = intBrithday;
}
foreach (DataColumn col in sourceDt.Columns)
{
if (col.ColumnName != "StudentId" && col.ColumnName != "PhontNum" && col.ColumnName != "StuBrithday")
{
newRow[col.ColumnName] = row[col.ColumnName];
}
}
//通过SQLBulkCopy导入
BulkCopy(copyDt);
}
}
static void BulkCopy(DataTable dt)
{
//将与数据库中数据类型一致的DataTable通过BulkCopy类导入数据库
string connString = "";
SqlConnection connection = new SqlConnection(connString);
connection.Open();
SqlBulkCopy sqlBulk = new SqlBulkCopy(connection);
//指定要导入的表名
sqlBulk.DestinationTableName = "StudentDB";
//读取到的excel表中的列名可能使用汉字
//如有需要,要将DataTable中列名与数据库中的列名做好映射
sqlBulk.ColumnMappings.Add("学生ID", "StudentId");
sqlBulk.ColumnMappings.Add("姓名", "StudentName");
sqlBulk.ColumnMappings.Add("生日", "StuBrithday");
sqlBulk.ColumnMappings.Add("电话", "PhontNum");
sqlBulk.WriteToServer(dt);
connection.Close();
}