7

我的问题:本周早些时候,我接到了加快我们程序中的一项任务的任务。我看了看,立即想到在该任务中为函数使用并行 foreach 循环。

我实现了它,遍历了函数(包括所有子函数)并更改了 SqlConnections(和其他东西),因此它能够并行运行。我开始了整个事情,一切都进行得又好又快(仅此一项就将完成该任务的时间减少了约 45%)

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

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

connection.Open()

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

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

这是用于向数据库发送更新/插入/删除语句的过程:

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);
        }
    }
}

以防万一:在较小的数据库上可能会发生死锁(错误号 2146232060),所以如果发生死锁,我必须让冲突语句在不同的时间发生。即使在小型数据库/小型服务器上也能很好地工作。如果错误不是由死锁引起的,则很可能是连接有问题,所以我正在清理所有断开的连接。

存在用于执行标量、填充数据表/数据集(是的,应用程序很)和执行存储过程的类似函数。

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

有人知道那里会发生什么吗?或者关于如何找出那里发生了什么的想法?

*编辑命令对象:

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

关于锁:如果我把锁收起来,我会收到几十个“连接已关闭”或“连接已打开”错误,因为 Open() 函数只是从 .NET 的连接池中获取一个连接。锁确实按预期工作。

示例代码:

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

*编辑 2

我必须更正:它挂在这个

command.Connection = connection;

至少我猜是这样,因为当我暂停应用程序时,“步骤”标记是绿色的并且亮着

command.ExecuteNonQuery();

说那是接下来要执行的语句。

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

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

*edit 5 我感觉这只发生在对特定数据库过程的一次特定调用中。我不知道为什么。在 C# 方面,该调用与其他调用没有区别,请 参见编辑 6。而且由于它当时没有执行该语句(我猜。也许有人可以纠正我。如果在调试模式下,一行是绿色标记(而不是黄色)它还没有执行该语句但等待该行之前的语句完成,对吗?)这很奇怪。

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

*edit 7 好的,实际上只有当一个特定的存储过程被调用时。除了它挂在连接对象的分配上(下一行标记为绿色)。试图找出造成这种情况的原因是atm。

*edit 8 是的,它只是发生在另一个命令中。就是这样。

*编辑 9 好的。问题解决了。“挂起”实际上是设置为 10 分钟(!)的 CommandTimeouts。它们只为两个命令设置(我在编辑 7 中提到的一个和我在编辑 8 中提到的一个)。由于我在重组命令以使它们像 devundef 建议的那样时找到了它们,因此我将他的答案标记为解决我的问题的答案。此外,他关于限制我的 for 循环使用的线程数量的建议进一步加快了进程。

特别感谢 Marc Gravell 解释了一些东西并在星期六和我一起呆在这里;)

4

1 回答 1

4

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

在并行循环内使用的任何数据都必须在循环内创建,或者必须具有适当的同步代码,以确保一次只有 1 个线程可以访问该特定对象。我在ExecuteNonQuerySafely. a)锁定连接在那里没有效果,因为连接对象是在方法内部创建的。b) 锁定命令并不能保证线程安全 - 可能您在将命令参数锁定在方法内之前设置了命令参数。如果在调用之前锁定命令Alock(command)将起作用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 ...
      }          

   }

}

并使用:

ExecuteCommandSafely((command) => {
   command.CommandText = "...";
   ... set parameters ..
   command.ExecuteNonQuery();
});

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

于 2012-08-18T20:08:18.143 回答