首页 > 解决方案 > 来自后台工作者冲突的查询?

问题描述

如果我使用相同的 ConnectionString 从多个线程执行查询,是否会出现问题?如果两个或多个线程同时尝试发送数据会发生什么?

string globalConnectionString = @"some_stringHere!";

//create new backgroundWorker if new logFile is created (txt file).
// ....
    
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
   // get some data from created logFile
   string serialNumber = getSerialNumber(logFile);
   string testResult = getTestResult(logFile);
       
   // if server is online, send data
   if(serverIsOnline)
   {
      using(SqlConnection connection = new SqlConnecton(globalConnectionString))
      {
         SqlCommand someCommand = new SqlCommand("some insert/update command here!", connection);
         connection.Open();
         Command.ExecuteNonQuery();
         connection.Close();
      }
   }
}

标签: c#multithreadingbackgroundworker

解决方案


如果使用正确,并发连接是可以的

假设出于正确的原因,同时使用多个连接没有问题。数据库可以处理数千个并发客户端连接。

并行执行相同的慢查询以使其更快完成可能会使其更慢,因为每个连接都可能阻塞其他连接。许多数据库已经对查询处理进行了并行化,产生的结果比粗略的客户端并行性要好得多。

如果你想让一个慢查询更快,你可以通过调查它为什么慢并修复性能问题来获得更好的结果。例如,如果您想插入 10K 行,使用 SqlBulkCopy 或BULK INSERT加载行比执行 10K INSERT 更快,这最终会相互阻塞以访问同一个表甚至数据页

您可以使用同一个连接来执行异步查询(例如 withExecuteNonQueryAsync()ExecuteReaderAsync(),前提是它们一个接一个地执行。您不能在同一个连接上执行多个并发查询,至少在不经过一些循环的情况下是这样。

真正的问题

真正的问题是首先使用 BackgroundWorker。自 2012 年async/await引入该类以来,该类已过时。使用 BGW 组合多个异步操作非常困难。进度报告可通过Progress<T>课程获得,合作取消可通过CancellationTokenSource。查看4.5 中的异步:在异步 API 中启用进度和取消以获取详细说明。

您可以仅将代码中的 BGW 调用替换为await command.ExecuteNonQueryAsync(). 您可以创建一个异步方法来执行将数据插入数据库:

private async Task InsertTestData(string serialNumber,string testResult)
{
   // if server is online, send data
   if(serverIsOnline)
   {
      using(SqlConnection connection = new SqlConnecton(globalConnectionString))
      {
         var someCommand = new SqlCommand("some insert/update command here!", connection);
         someCommand.Parameters.Add("@serial",SqlDbType.NVarChar,30).Value=serialNumber;
         ...
         connection.Open();
         Command.ExecuteNonQueryAsync();
      }
   }
}

如果检索序列号和测试数据很耗时,您可以使用Task.Run它们在后台运行它们:

   string serialNumber = await Task.Run(()=>getSerialNumber(logFile));
   string testResult = await Task.Run(()=>getTestResult(logFile));
   await InsertTestData(serialNumber,testResult);

您还可以使用像Dapper这样的库来简化数据库:

private async Task InsertTestData(string serialNumber,string testResult)
{
   // if server is online, send data
   if(serverIsOnline)
   {
      using(SqlConnection connection = new SqlConnecton(globalConnectionString))
      {
         await connection.ExecuteAsync("INSERT .... VALUES(@serial,@test)",
                               new {serial=serialNumber,test=testResults});
      }
   }
}

Dapper 将生成一个参数化查询,并将查询中的参数与匿名对象中的属性按名称进行匹配。


推荐阅读