C # Многопоточность транзакций вызывает исключения - PullRequest
1 голос
/ 24 мая 2019

Я пытаюсь смоделировать случай, с которым мы сталкиваемся, когда оператор 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 на ноль, а следующий поток пытается выполнить _transaction.commit() Я получаю NullReferenceException

Ссылка на объект не установлена ​​на экземпляр объекта.

        public void CommitTranzaction()
       {
        _transaction.Commit();
        _transactionConnection.Close();
        _transaction = null;
        _transactionConnection = null;
       }

, как если бы это был один и тот же экземпляр _transaction и _transactionConnection для всех 4 потоков, я знаю, что нам здесь чего-то не хватает в обработке многопоточности, но что это, на данный момент, чтобы разблокировать себя, я переместилwarehouseHelper.BeginTransaction(); оператор вне параллельного выполнения, так что теперь он находится сразу после try{, а оператор warehouseHelper.CommitTranzaction(); находится вконец блока try{ вне параллельного выполнения также, что является правильным подходом здесь.

Ответы [ 2 ]

3 голосов
/ 24 мая 2019

Экземпляр WarehouseHelper является общим для всех потоков, и когда первый поток вызывает CommitTranzaction, он устанавливает для переменной _transaction значение null, а когда другие потоки вызывают _transaction.Commit(), они выбрасывают NullRefernceException Я думаю warehouseHelper.CommitTranzaction() должно быть вне Parallel.ForEach петли.

1 голос
/ 24 мая 2019

Откройте соединение с базой данных внутри потока и начните транзакцию перед циклом и подтвердите после завершения цикла.

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);
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...