gpt4 book ai didi

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

转载 作者:太空狗 更新时间:2023-10-29 23:33:08 34 4
gpt4 key购买 nike

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

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

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

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

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
好吧,我错了。我删除了 lock 语句并且……它仍然有效。也许我第一次尝试它时有一个重用的连接或其他东西。谢谢你指出来。

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

*编辑6
有 3 个命令对象一直被重用。它们是在并行函数之上定义的。我不知道那是/曾经有多糟糕。它们只用于调用一个存储过程(每个调用一个不同的过程),当然有不同的参数和一个新的连接(通过上面提到的方法)。

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

*编辑8
是的,它只是在另一个命令下发生的。所以就是这样。

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

特别感谢 Marc Gravell 解释了一些事情,并在周六和我一起呆在这里;)

最佳答案

我认为问题可以在您的编辑 6 中找到:编辑 6:...3 个命令对象一直被重用。
在并行循环内使用的任何数据都必须在循环内创建,或者它必须具有适当的同步代码以确保一次只有 1 个线程可以访问该特定对象。我在 ExecuteNonQuerySafely 中没有看到这样的代码.
a) 锁定连接在那里没有效果,因为连接对象是在方法内部创建的。
b) 锁定命令并不能保证线程安全——可能你在方法内部锁定它之前设置了命令参数。一个 lock(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 )。

关于c# - SqlConnections、Parallel.For、一个旧的 C# 应用程序和随机挂起初始化 SqlConnections,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12020773/

34 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com