`
yacki
  • 浏览: 46279 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类

C#使用读写锁解决SQLITE并发异常问题

阅读更多
使用C#访问sqlite时,常会遇到多线程并发导致SQLITE数据库损坏的问题。

SQLite是文件级别的数据库,其锁也是文件级别的:多个线程可以同时读,但是同时只能有一个线程写。Android提供了SqliteOpenHelper类,加入Java的锁机制以便调用。但在C#中未提供类似功能。

作者利用读写锁(ReaderWriterLock),达到了多线程安全访问的目标。

using System;
using System.Collections.Generic;
using System.Text;
using System.Data.SQLite;
using System.Threading;
using System.Data;

namespace DataAccess
{

/////////////////
public sealed class SqliteConn
{
    private bool m_disposed;
    private static Dictionary<String, SQLiteConnection> connPool = 
        new Dictionary<string, SQLiteConnection>();
    private static Dictionary<String, ReaderWriterLock> rwl = 
        new Dictionary<String, ReaderWriterLock>();
    private static readonly SqliteConn instance = new SqliteConn();
    private static string DEFAULT_NAME = "LOCAL";

    #region Init
    // 使用单例,解决初始化与销毁时的问题
    private SqliteConn()
    {
        rwl.Add("LOCAL", new ReaderWriterLock());
        rwl.Add("DB1", new ReaderWriterLock());
        connPool.Add("LOCAL", CreateConn("\\local.db"));
        connPool.Add("DB1", CreateConn("\\db1.db"));
        Console.WriteLine("INIT FINISHED");
    }

    private static SQLiteConnection CreateConn(string dbName)
    {
        SQLiteConnection _conn = new SQLiteConnection();
        try
        {
            string pstr = "pwd";
            SQLiteConnectionStringBuilder connstr = new SQLiteConnectionStringBuilder();
            connstr.DataSource = Environment.CurrentDirectory + dbName;
            _conn.ConnectionString = connstr.ToString();
            _conn.SetPassword(pstr);
            _conn.Open();
            return _conn;
        }
        catch (Exception exp)
        {
            Console.WriteLine("===CONN CREATE ERR====\r\n{0}", exp.ToString());
            return null;
        }
    }
    #endregion

    #region Destory
    // 手动控制销毁,保证数据完整性
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected void Dispose(bool disposing)
    {
        if (!m_disposed)
        {
            if (disposing)
            {
                // Release managed resources
                Console.WriteLine("关闭本地DB连接...");
                CloseConn();
            }
            // Release unmanaged resources
            m_disposed = true;
        }
    }

    ~SqliteConn()
    {
        Dispose(false);
    }

    public void CloseConn()
    {
        foreach (KeyValuePair<string, SQLiteConnection> item in connPool)
        {
            SQLiteConnection _conn = item.Value;
            String _connName = item.Key;
            if (_conn != null && _conn.State != ConnectionState.Closed)
            {
                try
                {
                    _conn.Close();
                    _conn.Dispose();
                    _conn = null;
                    Console.WriteLine("Connection {0} Closed.", _connName);
                }
                catch (Exception exp)
                {
                    Console.WriteLine("严重异常: 无法关闭本地DB {0} 的连接。", _connName);
                    exp.ToString();
                }
                finally
                {
                    _conn = null;
                }
            }
        }
    }
    #endregion

    #region GetConn
    public static SqliteConn GetInstance()
    {
        return instance;
    }

    public SQLiteConnection GetConnection(string name)
    {
        SQLiteConnection _conn = connPool[name];

        try
        {
            if (_conn != null)
            {
                Console.WriteLine("TRY GET LOCK");
                //加锁,直到释放前,其它线程无法得到conn
                rwl[name].AcquireWriterLock(3000);
                Console.WriteLine("LOCK GET");
                return _conn;
            }
        }
        catch (Exception exp)
        {
            Console.WriteLine("===GET CONN ERR====\r\n{0}", exp.StackTrace);
        }
        return null;
    }

    public void ReleaseConn(string name)
    {
        try
        {
            //释放
            Console.WriteLine("RELEASE LOCK");
            rwl[name].ReleaseLock();
        }
        catch (Exception exp)
        {
            Console.WriteLine("===RELEASE CONN ERR====\r\n{0}", exp.StackTrace);
        }
    }

    public SQLiteConnection GetConnection()
    {
        return GetConnection(DEFAULT_NAME);
    }

    public void ReleaseConn()
    {
        ReleaseConn(DEFAULT_NAME);
    }
    #endregion
}

}
////////////////////////


调用的代码如下:

SQLiteConnection conn = null;
try
{
    conn = SqliteConn.GetInstance().GetConnection();
   //在这里写自己的代码
}
finally
{
    SqliteConn.GetInstance().ReleaseConn();
}

值得注意的是,每次申请连接后,必须使用ReleaseConn方法释放,否则其它线程就再也无法得到连接了。

安全起见,在作者写的这个工具类中,启用了最严格的读写锁限制(即在写入时无法读取)。如果数据读取频繁,读者亦可开发一个得到只读连接的方法以提高性能。

在Winxp/Win7/Win8/Win8.1 32/64位下测试通过。
2
0
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics