我的问题:本周早些时候,我接到了加快我们程序中的一项任务的任务。我看了看,立即想到在该任务中为函数使用并行 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 解释了一些东西并在星期六和我一起呆在这里;)