Потокобезопасный объект доступа к данным C #

1

Я пытаюсь сделать потокобезопасный уровень доступа к данным (вроде как оболочка клиента данных SQL). Какие шаги я должен предпринять, чтобы сделать этот поток безопасным, при этом максимальная производительность.

Например, если я добавлю блокировку на sqlConn перед закрытием соединения (поскольку он реализует IDisposable); что, если соединение находится в середине транзакции или запроса?

В общем, я пытаюсь выполнить поточно-безопасное решение; но в то же время я не хочу рисковать критическими исключениями или любыми задержками. Есть ли способ, которым я могу назначить приоритет закрывающей нити?

public class SQLWrapper : IDisposable
    {
        private SqlConnection _sqlConn;

        public SQLWrapper(string serverName_, string dbName_)
        {
            SqlConnectionStringBuilder sqlConnSB = new SqlConnectionStringBuilder()
            {
                DataSource = serverName_,
                InitialCatalog = dbName_,
                ConnectTimeout = 30,
                IntegratedSecurity = true,
            };

            sqlConnSB["trusted_connection"] = "yes";

            this.start(sqlConnSB.ConnectionString);
        }

        public SQLWrapper(string connString_)
        {
            this.start(connString_);
        }

        private void start(string connString_)
        {
            if (string.IsNullOrEmpty(connString_) == true)
                throw new ArgumentException("Invalid connection string");

            **lock (this._sqlConn)**
            {
                this._sqlConn = new SqlConnection(connString_);
                this._sqlConn.Open();
            }
        }

        private void CloseConnection()
        {
            **lock (this._sqlConn)**
            {
            this._sqlConn.Close();
            this._sqlConn.Dispose();
            this._sqlConn = null;
            }
        }
    }
Теги:
multithreading
sqlclient

2 ответа

4
Лучший ответ

Шаг, который вы должны сделать, это:

НЕ делает его потокобезопасным.

Просто.

Каждый поток должен иметь свою собственную копию с блокировкой/синхронизацией в базе данных.

Это также будет масштабироваться на разных компьютерах.

Это стандартный подход за последние 20 лет или около того.

Итак, каждый поток создает новый SqlWrapper, и все в порядке.

0

База данных выполняет пул соединений для вас; опираясь на него как можно больше. Вы действительно не должны блокировать.

Опция 1.

  • SqlConnection не инкапсулируется классом DAO; необходимо использовать соответствующие структуры и хранить строку соединения на уровне метода.

    public class SomeDAO
    {
        private readonly string _connectionString;
    
        public SomeDAO(string dsn)
        {
            _connectionString = dsn;
        }
    
        public IEnumerable<AssetVO> DoWork()
        {
            const string cmdText = "SELECT [AssetId] FROM [dbo].[Asset]";
    
            using (var conn = new SqlConnection(_connectionString))
            {
                conn.Open();
    
                using (var cmd = new SqlCommand(cmdText, conn))
                using (var dr = cmd.ExecuteReader())
                {
                    while (dr.Read())
                    {
                        yield return new AssetVO
                        {
                            AssetId = Guid.Parse(dr["AssetId"].ToString()),
                        };
                    }
                }
            }
        }
    }
    

Вариант 2.

  • SqlConnection является членом класса; это состояние тщательно поддерживается вспомогательными методами. Использование синтаксиса используется для SqlDataReader и SqlCommand.

    public class SomeDAO : IDisposable
    {
        #region backing store
    
        private readonly SqlConnection _connection;
    
        #endregion
    
        public SomeDAO(string dsn)
        {
            _connection = new SqlConnection(dsn);
        }
    
        public SqlConnection OpenConnection()
        {
            if (_connection.State != ConnectionState.Closed)
                _connection.Open();
    
            return _connection;
        }
    
        public void CloseConnection()
        {
            if (_connection.State != ConnectionState.Closed)
                _connection.Close();
        }
    
        public IEnumerable<AssetVO> DoWork()
        {
            const string cmdText = "SELECT [AssetId] FROM [dbo].[Asset]";
    
            try
            {
                using (var cmd = new SqlCommand(cmdText, OpenConnection()))
                using (var dr = cmd.ExecuteReader())
                {
                    while (dr.Read())
                    {
                        yield return new AssetVO
                        {
                            AssetId = Guid.Parse(dr["AssetId"].ToString()),
                        };
                    }
                }
            }
            finally
            {
                CloseConnection();
            }
        }
    
        #region Implementation of IDisposable
    
        /// <summary>
        ///   Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <filterpriority>2</filterpriority>
        public void Dispose()
        {
            _connection.Dispose();
        }
    
        #endregion
    }
    

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

    private static volatile bool _done;

    private static void Main()
    {
        #region keyboard interrupt

        ThreadPool.QueueUserWorkItem(delegate
        {
            while (!_done)
            {
                if (!Console.KeyAvailable) continue;
                switch (Console.ReadKey(true).Key)
                {
                    case ConsoleKey.Escape:
                        _done = true;
                        break;
                }
            }
        });

        #endregion

        #region start 3 threads in the pool

        ThreadPool.QueueUserWorkItem(DatabaseWorkerCallback);
        ThreadPool.QueueUserWorkItem(DatabaseWorkerCallback);
        ThreadPool.QueueUserWorkItem(DatabaseWorkerCallback);

        #endregion

        Thread.Sleep(Timeout.Infinite);
    }

    private static void DatabaseWorkerCallback(object state)
    {
        Console.WriteLine("[{0}] Starting", Thread.CurrentThread.ManagedThreadId);

        Thread.Sleep(1000);

        while (!_done)
        {
            using (var dao = new SomeDAO(Properties.Settings.Default.DSN))
            {
                foreach (var assetVo in dao.DoWork())
                    Console.WriteLine(assetVo);
            }
        }

        Console.WriteLine("[{0}] Stopping", Thread.CurrentThread.ManagedThreadId);
    }

Ещё вопросы

Сообщество Overcoder
Наверх
Меню