我正在尝试模拟一个我们在多线程进程中执行insert
语句并导致异常的情况,
class Program
{
private readonly static string[] names = { "name1", "name2", "name3", "name4" };
private const string CreateQuery = @"DROP TABLE IF EXISTS name_multi_thread_test CASCADE;
CREATE TABLE name_multi_thread_test (name VARCHAR(20));";
private const string InsertQuery = @"INSERT INTO name_multi_thread_test VALUES('{0}');";
private const string SelectQuery = "SELECT * FROM name_multi_thread_test;";
static void Main(string[] args)
{
var warehouseHelper = new WarehouseHelper();
try
{
System.Threading.Tasks.Parallel.ForEach(names, name =>
{
//foreach(var name in names)
//{
for(int i = 0; i < 10; i++)
{
warehouseHelper.BeginTransaction();
warehouseHelper.ExecuteNonQuery(CreateQuery);
warehouseHelper.ExecuteNonQuery(string.Format(InsertQuery, name));
using (var reader = warehouseHelper.ExecuteReader(SelectQuery))
{
while (reader.Read())
{
Console.WriteLine(reader["name"]);
}
}
warehouseHelper.CommitTranzaction();
}
}//;
);
}
catch(Exception ex)
{
Console.Write(ex.Message);
}
}
}
class WarehouseHelper
{
private IDbConnection _transactionConnection;
private IDbTransaction _transaction;
public void ExecuteNonQuery(string commandText)
{
var connection = GetConnection();
using (var command = connection.CreateCommand())
{
command.Transaction = _transaction;
command.CommandText = commandText;
command.ExecuteNonQuery();
}
}
public IDataReader ExecuteReader(string commandText)
{
var connection = GetConnection();
using (var command = connection.CreateCommand())
{
command.Transaction = _transaction;
command.CommandText = commandText;
return command.ExecuteReader();
}
}
public void BeginTransaction()
{
_transactionConnection = ConnectionManager.CreateConnection();
_transactionConnection.Open();
_transaction = _transactionConnection.BeginTransaction();
}
public void CommitTranzaction()
{
_transaction.Commit();
_transactionConnection.Close();
_transaction = null;
_transactionConnection = null;
}
private IDbConnection GetConnection()
{
if(_transactionConnection != null)
{
return _transactionConnection;
}else
{
var connection = ConnectionManager.CreateConnection();
connection.Open();
return connection;
}
}
}
class ConnectionManager
{
private static string _connectionStringOdbc = "Driver={Vertica};SERVER=x.x.x.x;PORT=5433;DATABASE=mydb;UID=username;PWD=password;";
public static OdbcConnection CreateConnection()
{
return new OdbcConnection(_connectionStringOdbc);
}
}
所以基本上,对于names
中的每个名称,我开始一个事务,创建一个表,插入一些条目,然后读取它们,并且在for循环中依次重复十次,结果是第一次迭代运行良好,直到执行达到CommitTranzation
,在此示例中,我们有4个线程,一旦第一个线程将_transaction
设置为null,下一个线程尝试执行_transaction.commit()
,我将得到NullReferenceException >
对象引用未设置为对象的实例。
public void CommitTranzaction()
{
_transaction.Commit();
_transactionConnection.Close();
_transaction = null;
_transactionConnection = null;
}
好像所有4个线程都是_transaction和_transactionConnection的相同实例一样,我知道我们在处理多线程时缺少了一些东西,但这是什么,
现在为了解除阻止自己,我将warehouseHelper.BeginTransaction();
语句移到了并行执行之外,所以现在它紧接在try{
之后,并且warehouseHelper.CommitTranzaction();
语句位于try{
块的末尾在并行执行之外,这里正确的方法是什么。
答案 0 :(得分:3)
WarehouseHelper
实例对于所有线程都是公共的,当第一个线程调用CommitTranzaction
时,它将_transaction
变量设置为null,而其他线程调用_transaction.Commit()
他们丢了NullRefernceException
我认为warehouseHelper.CommitTranzaction()
应该在Parallel.ForEach
循环之外。
答案 1 :(得分:1)
在线程内部打开数据库连接,并在循环之前启动事务,并在循环结束之后提交。
static void Main(string[] args)
{
try
{
System.Threading.Tasks.Parallel.ForEach(names, name =>
{
var warehouseHelper = new WarehouseHelper();
warehouseHelper.BeginTransaction();
for(int i = 0; i < 10; i++)
{
warehouseHelper.ExecuteNonQuery(CreateQuery);
warehouseHelper.ExecuteNonQuery(string.Format(InsertQuery, name));
}
using (var reader = warehouseHelper.ExecuteReader(SelectQuery))
{
while (reader.Read())
{
Console.WriteLine(reader["name"]);
}
}
warehouseHelper.CommitTranzaction();
}
);
}
catch(Exception ex)
{
Console.Write(ex.Message);
}
}