问题
I have a class of range
public class avl_range
{
public long start { get; set; }
public long end { get; set; }
}
If I use a normal FOR
works perfect, but have to wait for each command to finish and each query take 8 seconds, so 10 queries take 80 seconds.
In the Parallel version If I only print the ranges works perfect, but if try to execute the command say is already in progress.
{"An operation is already in progress."}
How can I solve this?
var numbers = new List<avl_range>();
using (var conn = new NpgsqlConnection(strConnection))
{
conn.Open();
Action<avl_range> forEachLoop = number => //Begin definition of forLoop
{
// only the console write line works ok
Console.WriteLine(number.start + " - " + number.end);
using (var cmd = new NpgsqlCommand())
{
cmd.Connection = conn;
cmd.CommandText = String.Format( "SELECT * FROM avl_db.process_near_link({0}, {1});"
, number.start
, number.end);
// here cause the error.
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
Console.WriteLine(reader.GetString(0));
}
}
}
};
Parallel.ForEach(numbers, forEachLoop);
}
);
FYI: Im trying to solve this issue I post it before
回答1:
An Npgsql connection can't be used concurrently - only one command may be running at any given point in time (in other words, no MARS support).
It may definitely make sense to open multiple connections to perform your queries in parallel. Although establishing a new physical connection is expensive, connection pooling is extremely lightweight, so there's very little overhead in reusing physical connections. The main reason not to do this is if you need your multiple operations to be in the same transaction.
回答2:
Even if you could get it to work with MARS, connection objects are almost never thread safe anyway, you need to have a connection per thread. Parallel.ForEach has overloads to make this easy which have functions that run at the start of a thread and at the end.
var numbers = new List<avl_range>();
Func<NpgsqlConnection> localInit => () =>
{
var conn = new NpgsqlConnection(strConnection);
conn.Open();
};
Action<NpgsqlConnection> localFinally = (conn) => conn.Dispose();
Func<avl_range, ParallelLoopState, NpgsqlConnection, NpgsqlConnection> forEachLoop = (number, loopState, conn) => //Begin definition of forLoop
{
// only the console write line works ok
Console.WriteLine(number.start + " - " + number.end);
using (var cmd = new NpgsqlCommand())
{
cmd.Connection = conn;
cmd.CommandText = String.Format( "SELECT * FROM avl_db.process_near_link({0}, {1});"
, number.start
, number.end);
// here cause the error.
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
Console.WriteLine(reader.GetString(0));
}
}
}
return conn;
};
Parallel.ForEach(numbers, localInit, forEachLoop, localFinally);
That being said, most of the time doing concurrent connections to a database is not the right idea, the bottleneck is likely elsewhere and you should use a profiler to see what is really slowing your program down and focus your efforts there.
Sample code for comments:
var numbers = GetDataForNumbers();
List<string> results = new List<string>();
Func<List<string>> localInit => () => new List<string>();
Func<avl_range, ParallelLoopState, List<string>, List<string>> forEachLoop = (number, loopState, localList) => //Begin definition of forLoop
{
using (var conn = new NpgsqlConnection(strConnection))
{
conn.Open();
//This line is going to slow your program down a lot, so i commented it out.
//Console.WriteLine(number.start + " - " + number.end);
using (var cmd = new NpgsqlCommand())
{
cmd.Connection = conn;
cmd.CommandText = String.Format( "SELECT * FROM avl_db.process_near_link({0}, {1});"
, number.start
, number.end);
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
//Add a object to the thread local list, we don't need to lock here because we are the only thread with access to it.
localList.Add(reader.GetString(0));
}
}
}
}
return localList;
};
Action<List<String>> localFinally = localList =>
{
//Combine the local list to the main results, we need to lock here as more than one thread could be merging at once.
lock(results)
{
results.AddRange(localList);
}
};
Parallel.ForEach(numbers, localInit, forEachLoop, localFinally);
//results now contains strings from all the threads here.
来源:https://stackoverflow.com/questions/41902019/can-i-use-parallel-for-with-sql-commands