线程安全数据访问对象C#

时间:2014-05-20 13:21:12

标签: c# sql multithreading sqlclient

我试图创建一个线程安全的数据访问层(有点像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;
            }
        }
    }

2 个答案:

答案 0 :(得分:6)

您应该采取的措施是:

不使其线程安全。

简单。

每个线程都应该拥有它自己的副本,数据库上会发生锁定/同步。

它也可以跨计算机扩展。

这是过去20年左右的标准方法。

因此,每个线程都会创建一个新的SqlWrapper,一切都很好。

答案 1 :(得分:0)

数据库为您执行连接池;尽可能地依靠它。你真的不应该要求锁定。

选项1。

  • DAO类不封装SqlConnection;在方法级别需要适当的使用结构并存储连接字符串。

    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);
    }