C#多线程事务导致异常

时间:2019-05-24 11:29:05

标签: c# multithreading odbc

我正在尝试模拟一个我们在多线程进程中执行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{块的末尾在并行执行之外,这里正确的方法是什么。

2 个答案:

答案 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);
    }
}
相关问题