通过单线程读取特定文件类型

时间:2019-06-13 07:03:56

标签: c# .net multithreading file

我有一个包含多个文件的文件夹,我打算读取这些文件并将其插入类型指定的表中。

问题:

我正在读取具有多个线程的文件,但是每次一个线程尝试读取另一个线程正在读取的文件的类型(字符串)时,它都会崩溃并显示错误:

  

事务(进程ID 69)在锁定时死锁|通讯   用另一个进程缓冲资源,并已被选为   死锁的受害者。重新运行事务。在   System.Data.SqlClient.SqlConnection.OnError(SqlException异常,   布尔值breakConnection,动作1 wrapCloseInAction) at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose) at System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady) at System.Data.SqlClient.SqlCommand.RunExecuteNonQueryTds(String methodName, Boolean async, Int32 timeout, Boolean asyncWrite) at System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(TaskCompletionSource 1   完成,字符串methodName,布尔型sendToPipe,Int32超时,   布尔值和usedCache,布尔值asyncWrite,布尔值inRetry)   System.Data.SqlClient.SqlCommand.ExecuteNonQuery()在   Microsoft.Practices.EnterpriseLibrary.Data.Database.DoExecuteNonQuery(DbCommand   命令)   Microsoft.Practices.EnterpriseLibrary.Data.Database.ExecuteNonQuery(DbCommand   命令)

我可以用一个线程来完成,但是要花很多时间,使用多线程,如何避免崩溃?

代码:

 public int InsertData(string data, string tableName)
        {
            string query;
            var token = JToken.Parse(data)["data"];
            JArray jArrayData;
            if (token is JArray)
            {
                jArrayData = token as JArray;// JArray.Parse(data) as JArray;
                foreach (var item in jArrayData.Children())
                {
                    var itemProperties = item.Children<JProperty>();
                    query = ServerHelper.CreateInsertQuery(itemProperties, tableName);
                    ExecuteQuery(query);
                }
            }

            return 0;
        }

   private int ExecuteQuery(string query)
        {
            if(conn.State == ConnectionState.Closed)
            {
                conn.Open();
            }
            SqlCommand cmd = new SqlCommand(query, conn);
            return cmd.ExecuteNonQuery();
        }


 public static string CreateInsertQuery(JEnumerable<JProperty> itemProperties, string tableName)
        {
            string[] nameArray = itemProperties.Select(x => x.Name).ToArray();
            string[] valuesArray = itemProperties.Select(x => "'" + x.Value.ToString().Replace("'","''") + "'").ToArray();
            //check for null
              string query = "Insert Into " + tableName + " (" + string.Join(",", nameArray) + @")"
         + " values(" + string.Join(",", valuesArray) + ")";
        return query;
        }

尝试并行执行查询的代码是:

   public void PerformInitialization()
        {
            ts = new CancellationTokenSource();
            ct = ts.Token;

            ChangeProcToDat();

            string strMaxThreads = ConfigurationManager.AppSettings["MaxThreads"].ToString();
            if(!string.IsNullOrEmpty(strMaxThreads))
            {
                bool parseSuccess = Int32.TryParse(strMaxThreads, out maxThreads);
                if(parseSuccess == false)
                {
                    maxThreads = 1;
                }
            }

            processQueueTasks = new Task[maxThreads];

            processData = new ProcessData();

            for (int i = 0; i < maxThreads; i++)
            {
                processQueueTasks[i] = Task.Factory.StartNew(() => ProcessQueue(), ct);
            }
        }

 public void ProcessQueue()
    {
        bool cancelled = false;
        try
        {

            while (!ct.IsCancellationRequested)
            {
                try
                {

                    Directory.CreateDirectory(queueFolderPath);

                    DirectoryInfo queueFolderDi = new DirectoryInfo(queueFolderPath);

                    FileInfo datFile = queueFolderDi.GetFiles("*.dat", SearchOption.AllDirectories).OrderBy(f => f.LastWriteTime).ToList().FirstOrDefault();
                    while (datFile != null)
                    {
                        string processingFileName = datFile.FullName.Replace(".dat", ".proc");
                        FileInfo file = new FileInfo(processingFileName);
                        File.Move(datFile.FullName, processingFileName);

                        try
                        {                                

                            if (file.Name.Contains("_ABC_"))
                            {
                                var fileNameArr = file.Name.Split('_', '.');

                                var fileType = "";  # this is the type of the file
                                if (fileNameArr.Length >= 3)
                                {
                                    fileType = fileNameArr[3];
                                }

                                // the db part

                               var content = File.ReadAllText(file.FullName);

                                int responseCode = 0;
                                InsertData(content, reportType);

                                File.Delete(file.FullName);


                            }

                        }
                        catch (Exception ex)
                        {
                            Log.WriteToFailed("Error processing queue file " + file.Name + " "
                                + Environment.NewLine + ex.Message + Environment.NewLine + ex.StackTrace + Environment.NewLine);

                        }


                        datFile = queueFolderDi.GetFiles("*.dat", SearchOption.AllDirectories).OrderBy(f => f.LastWriteTime).ToList().FirstOrDefault();


                    }


                }
                catch (Exception ex)
                {
                    Log.WriteToFailed("Error processing files in the queue folder."
                                + Environment.NewLine + ex.Message + Environment.NewLine + ex.StackTrace + Environment.NewLine);

                }
                finally
                {
                    cancelled = ct.WaitHandle.WaitOne(1000);
                }


            }
        }
        catch (Exception ex)
        {
            Log.WriteToFailed("Error occured in send thread. Message:" + ex.Message + "\n Stack trace:" + ex.StackTrace);
        }
    }

1 个答案:

答案 0 :(得分:0)

类似于 actual 问题,是如何快速将大量JSON文件中的记录插入SQL Server表中。

性能和僵局

尝试的解决方案很慢,因为它试图插入记录逐行行(RBAR)。这是最慢的方法。

它死锁是因为它使用的连接寿命长,这意味着只要连接保持打开状态,行和表锁就会被累积并保持。如果表没有主键或非聚集索引,则SQL Server可能必须在数据页甚至表级别上获取更多锁,并在连接保持打开状态时保持它们。

在这种情况下使用线程处理会使情况变得更糟,因为多个连接会互相等待。如果缺少索引导致过度锁定,则连接可能最终锁定彼此所需的数据页,从而导致死锁。

许多种将JSON数据插入SQL Server的方法

SQL Server 2016及更高版本中的JSON

SQL Server 2016并具有可用于解析JSON字符串的JSON函数。可以编写一个存储过程,该过程使用JSON字符串作为参数,使用OPENJSON对其进行解析,然后将值插入表中。从OPENJSON – The easiest way to import JSON text into table窃取示例:

 INSERT INTO Person
 SELECT * 
 FROM OPENJSON(@json)
 WITH (id int,
       firstName nvarchar(50), lastName nvarchar(50), 
       isAlive bit, age int,
       dateOfBirth datetime2, spouse nvarchar(50))

将文件的全部内容发送到SQL Server不会比发送单个文本值使用更多数据。

OPENROWSET

另一种选择是直接使用OPENROWSET加载JSON文件,然后使用OPENJSON对其进行解析,例如:

INSERT INTO TargetTable(ID,Name,Price,Pages,Author)
SELECT book.id, book.name, book.price, book.pages_i, book.author
 FROM OPENROWSET (BULK 'C:\JSON\Books\books.json', SINGLE_CLOB) as j
 CROSS APPLY OPENJSON(BulkColumn)
 WITH( id nvarchar(100), name nvarchar(100), price float,
 pages_i int, author nvarchar(100)) AS book

客户端解析和SqlBulkCopy

另一种选择是在客户端(例如,使用JSON.NET)解析文本,并使用SqlBulkCopy大容量插入数据。 SqlBulkCopy使用BULK INSERTbcp使用的相同机制,以最少的日志记录尽可能快地插入数据。

FastMember's ObjectReader可用于将项目列表转换为SqlBulkCopy期望的IDataReader。代码可能是这样的:

var json=File.ReadAllText(path);
var data=JsonConvert.DeserializeObject<List<SomeType>>(json);
using(var bcp = new SqlBulkCopy(connection)) 
using(var reader = ObjectReader.Create(data, "Id", "Name", "Description")) 
{ 
  bcp.DestinationTableName = "SomeTable"; 
  bcp.WriteToServer(reader); 
}

或者,作为一种方法:

void ImportJson<T>(string path,string tableName,string[] fields)
{
    var json=File.ReadAllText(path);
    var data=JsonConvert.DeserializeObject<List<T>>(json);
    using(var bcp = new SqlBulkCopy(connection)) 
    using(var reader = ObjectReader.Create(data, fields)) 
    { 
        bcp.DestinationTableName = tableName; 
        bcp.WriteToServer(reader); 
    }
}

客户端解析和多行INSERT

代替执行单个INSERT语句,可以通过批处理或使用多行INSERT语句来提高性能,例如:

INSERT INTO TABLE TableA (Col1,Col2)
VALUES
(Val11,Val12),
(Val21,Val22)

尽管手工书写很无聊。一个人可以使用马克·格雷夫(Marc Gravell)的另一作品Dapper来执行多个插入操作:

var json=File.ReadAllText(path);
var data=JsonConvert.DeserializeObject<List<T>>(json);
conn.Execute("INSERT INTO TableA (Col1,Col2) VALUES(@Prop1,@Prop2)",data);

还有其他选择。数据可以作为表值参数发送,另一件事是easier to do with Dapper