my problem: Earlier this week, I got the task to speed up a task in our program. I looked at it and immediately got the idea of using a parallel foreach loop for a function in that task.
I implemented it, went through the function (including all sub-functions) and changed the SqlConnections (and other stuff) so it'd be able to run in parallel. I started the whole thing and all went well and fast (alone that reduced the time for that task by ~45%)
Now, yesterday we wanted to try the same thing with some more data and ...I got some weird problem: Whenever the parallel function got called, it did it's work...but sometimes one of the threads would hang for at least 4 minutes (timeouts are set to one minute, for connection AND command).
If I pause the program during that, I see that only one thread is still active from that loop and it hangs on
connection.Open()
After ~4 minutes the program simply proceeds without throwing an error (aside from a message in the Output box, saying that an exception somewhere occured, but it wasn't catched by my application but somewhere in the SqlConnection/SqlCommand object).
I can kill all connections on the MSSQLServer without anything happens, also the MSSQLServer does nothing during those 4 minutes, all connections are idle.
This is the procedure that is used for sending Update/Insert/Delete statements to the database:
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);
}
}
}
just in case: on smaller databases there can occur deadlocks (err number 2146232060), so if one occurs, I've to make the colliding statements occur in different time. Works great even on small databases/small servers. If the error wasn't caused by a deadlock, chances are that the connection was faulty, so I'm cleaning all broken connections.
Similiar functions exist for executing scalars, filling datatables/datasets (yes, the application is that old) and executing storedprocedures.
And yes all of those are used in the parallel loop.
Has someone any idea what could be going on there? Or an idea on how I can find out what is going on there?
*edit about the command object:
it is given to the function, the command object is always a new object when it is given into the function.
about the lock: If I put the lock away, I get dozens and hundreds of 'connection is closed' or 'connection is already open' errors, because the Open() function just get's a connection from .NET's connection pool. The lock does work as intended.
Example code:
using(SqlCommand deleteCommand = new SqlCommand(sqlStatement))
{
ExecuteNonQuerySafely(deleteCommand); // that's the function that contains the body I posted above
}
*edit 2
I've to make a correction: It hangs on this
command.Connection = connection;
at least I guess it does, because when I pause the application, the 'step' mark thingi is green and on
command.ExecuteNonQuery();
saying that that is the statement that'll be executed next.
*edit 3 just to be sure I just started another test without any locks around the connection object...will take some minutes to get the results.
*edit 4 well, I was wrong. I removed the lock statements and...it still worked. Maybe the first time I tried it there was a reused connection or something. Thank's for pointing it out.
*edit 5
I'm getting the feeling that this occurs only with one specific call to a specific database procedure. I don't know why. C# wise there is no difference between that call and other calls see edit 6. And since it didn't execute the statement at that point (I guess. Maybe someone can correct me on that. If, in debug mode, a line is green marked (instead of yellow) it didn't execute that statement yet but waits for the statement before that line to finish, is that correct?) it's strange.
*edit 6 There were 3 command objects that were reused the whole time. They were defined above the parallel function. I don't know how bad that is/was. They were only used to call one stored procedure (each of them called a different procedure), of course with different parameters and a new connection (through the above mentioned method).
*edit 7
ok, it's really only when one specific stored procedure is called. Except that it's on the assignment of the connection object that it hangs (next line is marked green).
Trying to figure out what the cause for that is atm.
*edit 8 yay, it just happened at another command. So that was that.
*edit 9 ok. Problem solved. The 'hangs' were actually CommandTimeouts that were set to 10 minutes(!). They were only set for two commands (the one I mentioned in edit 7 and the one that I mentioned in edit 8). Since I found both of them while I was restructuring my commands to make them like devundef suggested, I marked his answer as the one that solved my problem. Also his suggestion of limiting the amounts of threads my for-loop was using sped up the process even more.
Special thank's to Marc Gravell for explaining stuff and hanging in here with me on a saturday ;)
SqlCommand
object (which is a bad practice to begin with). Could you show how this code is actually used in the context of parallelizing your database access? – Exotericlock (connection)
does nothing, so take it out for the sake of simplicity. I don't see wherecommand
comes from, it's not shared, is it? Have you tried monitoring the number of held connection objects (up a shared count on open and decrement on close) to see how high it gets compared to the pool maximum? – Oneiric