简体   繁体   中英

Thread Safe Data Access Object C#

I'm trying to make a thread safe Data Access Layer (kind of like a SQL Data Client wrapper). What are some steps I should be making to make this thread safe, while maximizing performance.

For example, if i add a lock on the sqlConn before it closes the connection (since it implements IDisposable); what if the connection is in the middle of a transaction or query?

In summary, I'm trying to accomplish a thread-safe solution; but at the same time, I do not want to risk any critical exceptions, or any delays. Is there any way I can prioritize the closing thread?

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

The step you should do is:

NOT making it thread safe.

Simple.

Every thread should have it's own copy, with locking / synchronization happening on the database.

THen it will also scale across computers.

This is the standard approach for the last 20 years or so.

So, every thread creates a new SqlWrapper and everything is fine.

The database performs connection pooling for you; lean on it as much as you can. You really shouldn't require locking.

Option 1.

  • SqlConnection is not encapsulated by the DAO class; appropriate using structures and storage of the connection string is required at the method level.

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

Option 2.

  • The SqlConnection is a class member; it's state is carefully maintained by helper methods. Using syntax is used for SqlDataReader and 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 } 

Both solutions survive a threaded test without the need for explicit locking.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM