SqlConnections,Parallel.For,一个旧的C#应用​​程序和随机挂起初始化SqlConnections

Ste*_*ler 5 c# multithreading sqlconnection hang sql-server-2008-r2

我的问题:本周早些时候,我完成了加速我们计划任务的任务.我看着它,立即想到了在该任务中使用并行foreach循环的功能.

我实现了它,经历了函数(包括所有子函数)并更改了SqlConnections(以及其他东西),因此它可以并行运行.我开始了整个事情,一切顺利而且快速(单独将这项任务的时间缩短了约45%)

现在,昨天我们想用更多的数据尝试同样的事情......我遇到了一些奇怪的问题:每当调用并行函数时,它就完成了它的工作......但有时其中一个线程会挂起至少4个分钟(超时设置为一分钟,用于连接AND命令).

如果我在此期间暂停程序,我看到只有一个线程仍在该循环中处于活动状态并且它会挂起

connection.Open()
Run Code Online (Sandbox Code Playgroud)

大约4分钟之后,程序只是继续运行而不会抛出错误(除了输出框中的消息,说某个地方发生异常,但我的应用程序没有捕获它,而是在SqlConnection/SqlCommand对象中的某处).

我可以杀死MSSQLServer上的所有连接,没有任何事情发生,MSSQLServer在这4分钟内什么都不做,所有连接都是空闲的.

这是用于将Update/Insert/Delete语句发送到数据库的过程:

int i = 80;
bool itDidntWork = true;
Random random = new Random();
while (itDidntWork && i > 0)
{
    try
    {
        using (SqlConnection connection = new SqlConnection(sqlConnectionString))
        {
            connection.Open();
            lock (connection)
            {
                command.Connection = connection;
                command.ExecuteNonQuery();
            }

            itDidntWork = false;
        }
    }
    catch (Exception ex)
    {
        if (ex is SqlException && ((SqlException)ex).ErrorCode == -2146232060)
        {
            Thread.Sleep(random.Next(500, 5000));
        }
        else
        {
            SqlConnection.ClearAllPools();
        }

        Thread.Sleep(random.Next(50, 110));
        i--;
        if (i == 0)
        {
            writeError(ex);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

以防万一:在较小的数据库上可能会出现死锁(错误编号2146232060),因此如果发生死锁,我将使碰撞语句在不同的时间发生.即使在小型数据库/小型服务器上也能正常工 如果错误不是由死锁引起的,那么连接可能是错误的,所以我正在清理所有断开的连接.

用于执行标量,填充数据表存在Similiar功能/数据集(是的,该应用程序是旧)并执行storedprocedures.

是的,所有这些都用在并行循环中.

有人知道那里会发生什么吗?或者了解我如何才能知道那里发生了什么?

*编辑命令对象:

它被赋予函数,命令对象在被赋予函数时总是一个新对象.

关于锁:如果我把锁放开,我得到几十个'连接已关闭'或'连接已经打开'错误,因为Open()函数只是从.NET的连接池获得连接.锁可以按预期工作.

示例代码:

using(SqlCommand deleteCommand = new SqlCommand(sqlStatement))
{
    ExecuteNonQuerySafely(deleteCommand); // that's the function that contains the body I posted above
}
Run Code Online (Sandbox Code Playgroud)

*编辑2

我要纠正:它就是这个问题

command.Connection = connection;
Run Code Online (Sandbox Code Playgroud)

至少我猜它确实如此,因为当我暂停应用程序时,'step'标记thingi是绿色的

command.ExecuteNonQuery();
Run Code Online (Sandbox Code Playgroud)

说那是下次要执行的声明.

*编辑3只是为了确保我刚开始另一个测试而没有任何锁定连接对象...将需要几分钟才能得到结果.

*编辑4好,我错了.我删除了锁定语句......它仍然有效.也许是我第一次尝试它时有一个重用的连接或其他东西.谢谢你的指点.

*编辑5我感觉只有在对特定数据库过程的一次特定调用时才会发生这种情况.我不知道为什么.C#明智的是,该呼叫与其他呼叫之间没有区别,请 参阅编辑6.并且因为它在那时没有执行语句(我猜.也许有人可以纠正我.如果在调试模式下,一行是绿色标记(而不是黄色)它还没有执行该语句但是等待对于该行之前的陈述,这是正确的吗?)这很奇怪.

*编辑6有3个命令对象一直被重用.它们在并行函数之上定义.我不知道是多么糟糕/是.它们仅用于调用一个存储过程(每个存储过程称为不同的过程),当然使用不同的参数和新的连接(通过上述方法).

*编辑7确定,它实际上只在调用一个特定的存储过程时.除了它挂起的连接对象的分配(下一行标记为绿色).试图弄清楚原因是什么.

*编辑8 yay,它恰好发生在另一个命令.就是这样.

*编辑9好的.问题解决了.'hangs'实际上是CommandTimeouts,设置为10分钟(!).它们只设置了两个命令(我在编辑7中提到的那个和我在编辑8中提到的那个).由于我在重构命令时发现了这两个命令,使它们像devundef建议的那样,我将他的答案标记为解决我问题的答案.此外,他建议限制我的for-loop使用的线程数量,甚至更多地加快了这个过程.

特别感谢Marc Gravell在周六与我一起解释的东西和挂在这里;)

Mar*_*Zen 4

我认为问题可以在您的 edit 6: edit 6: ...3 命令对象一直被重用中找到。

并行循环内使用的任何数据都必须在循环内创建,或者必须具有适当的同步代码,以确保一次只有 1 个线程可以访问该特定对象。我在 .txt 文件中没有看到这样的代码ExecuteNonQuerySafely。a) 锁定连接在那里不起作用,因为连接对象是在方法内部创建的。b) 锁定命令并不能保证线程安全 - 可能您在将命令锁定在方法内之前设置了命令参数。lock(command)如果您在调用之前锁定命令,则A将会起作用ExecuteNonQuerySafely,但是并行循环内的锁定并不是一件好事 - 毕竟这是反并行的定义,最好完全避免这种情况并为每次迭代创建一个新命令。更好的是对 进行一些重构ExecuteNonQuerySafely,它可以接受回调操作而不是 SqlCommand。例子:

public void ExecuteCommandSafely(Action<SqlCommand> callback) {
   ... do init stuff ...
   using (var connection = new SqlConnection(...)) {
      using (var command = new SqlCommand() {
         command.Connection = connection;
         try{
            callback(command);
         }
         ... error handling stuff ...
      }          

   }

}
Run Code Online (Sandbox Code Playgroud)

并使用:

ExecuteCommandSafely((command) => {
   command.CommandText = "...";
   ... set parameters ..
   command.ExecuteNonQuery();
});
Run Code Online (Sandbox Code Playgroud)

最后,并行执行命令时遇到错误这一事实表明,在这种情况下并行执行可能不是一件好事。您正在浪费服务器资源来获取错误。连接成本很高,请尝试使用该MaxDegreeOfParalellism选项来调整此特定循环的工作负载(请记住,最佳值将根据硬件/服务器/网络/等而变化。)。该Parallel.ForEach方法有一个重载,它接受一个ParallelOptions参数,您可以在其中设置要为该参数并行执行的线程数量(http://msdn.microsoft.com/en-us/library/system.threading.tasks.paralleloptions)。最大并行度.aspx)。