Firebird在多线程环境中

时间:2018-03-26 17:42:05

标签: c# multithreading firebird

我创建了一个用于保存Firebird数据库连接的单例类,并且我正在执行查询(见下文),这个查询就像一个魅力,直到我不得不在其中一个应用程序中添加一些定时器来设置&定期检查一些数据。

现在我经常会收到错误消息“不支持并行事务”。我无法得到它,因为我在finally块中关闭了连接。

我已经花了很长时间在网上搜索并试图弄清楚如何接近,但似乎我需要支持。

以下是我fBird课程的主要部分:

public sealed class fBird {
    private FbConnection FbConn = new FbConnection(connectionString);
    static fBird m_oInstance = null;
    static readonly object m_oPadLock = new object();

    public static fBird Instance {
        get {
            lock (m_oPadLock) {
                if (m_oInstance == null) {
                    m_oInstance = new fBird();
                }
                return m_oInstance;
            }
        }
    }

    //Data query:
    public DataTable qSelect(string query){
        if(!getState().Equals("Open")) FbConn.Open();
        using (FbTransaction transaction = FbConn.BeginTransaction()) {
            try{
                FbCommand cmd = new FbCommand(query, FbConn, transaction);
                FbDataAdapter adpt = new FbDataAdapter(cmd);
                DataTable dt = new DataTable();
                adpt.Fill(dt);
                transaction.Commit();
                return dt;
            } catch (Exception ex){
                transaction.Rollback();
                throw new Exception("DataQuery: " + ex.Message);
            } finally {
                FbConn.Close();
            }
        }
    }

    //NonQuery query:
    public int NonQuery(string query){
        if(!getState().Equals("Open")) FbConn.Open();
        using (FbTransaction transaction = FbConn.BeginTransaction()) {
            try{
                FbCommand cmd = new FbCommand(query, FbConn, transaction);
                int i = cmd.ExecuteNonQuery();
                transaction.Commit();
                return i;
            } catch (Exception ex){
                transaction.Rollback();
                throw new Exception("NonQuery: "  + ex.Message);
            } finally {
                FbConn.Close();
            }
        }
    }
}

以下是我如何使用该课程:

DataTable dt = fBird.Instance.qSelect("SELECT * FROM USERS");
int i = fBird.Instance.NonQuery("DELETE FROM TABLE");

以下是我使用计时器的方法:

public partial class MainForm : Form {
    private System.Timers.Timer aTimer;

    ...

    void setTimers() {
        aTimer = new System.Timers.Timer(1500));
        aTimer.Elapsed += OnTimedEvent;
        aTimer.AutoReset = true;
        aTimer.Enabled = true;
    }

    void OnTimedEvent(Object source, ElapsedEventArgs e) {
        try{

            //different changes to & reads from database

        } catch(Exception ex) {
            MessageBox.Show("OnTimedEvent: " + ex.Message);
        }
    }
}

我发现的是,由于某种原因,Firebird的连接不止一个(单身不应该允许,对吧?)。我可以在网络选项卡上的Windows性能监视器中看到它。作为测试我做了非常不同的操作,打开不同的窗口和放大器。应用程序中的屏幕,也保存一些更改。随机我得到错误(并行事务)并创建新连接,并且一段时间(如30-40次点击)一切正常。

这让我疯狂。我希望你们中的某个人能够帮助我。我觉得我在这里做了一些非常错的事,但我找不到它。如果您需要更多信息,请与我们联系。

3 个答案:

答案 0 :(得分:1)

如果您使用多个线程,那么您应该共享一个连接。您的问题不是多个连接之一,而是多个线程使用相同的连接,并尝试创建自己的事务,驱动程序明确不支持。您的代码也可能受到其他竞争条件的影响。

停止使用该单身人士。而是为每个工作单元获取一个新连接,并在完成后关闭它。默认情况下,Firebird ADO.net提供程序提供连接池,因此它相对便宜。

就个人而言,我会完全摆脱那个fBird类,但至少要摆脱共享实例,实现IDisposable来关闭dispose上的连接,添加一个静态方法来创建实例一经请求。然后,您应该在fBird中使用这些using个实例,以确保它在最后得到正确关闭。

我建议摆脱那个fBird类的原因是,你只是包装了普通的连接对象,并提供了更糟糕和更脆弱的抽象。

答案 1 :(得分:0)

您需要使用锁定,单身人士不需要单个用户

答案 2 :(得分:0)

您的fBird课程有三件事可能需要重新考虑。

首先,正如其他答案所指出的那样,FBConnection对象不是线程安全的。 (这是Firebird的设计/限制,而不是C#库特定的行为)。这意味着您可以:

  • 确保每个线程都有自己的此类实例; OR
  • 确保序列化对单个连接的访问​​(即使用锁定())

其次,您的方法非常繁重,因为您每次都在进行完全连接/断开连接。当你调高音量时,这会让事情变慢。

第三,你总是在NonQuery中提交。如果您的操作涉及多个查询,那么如果两者之间出现故障,您的数据库可能会保持不一致。

这是一个想法,作为前两点的妥协解决方案。在我的一个旧应用程序(不是c#sorry)中,我创建了一个连接代理来减少restful服务中的连接/断开开销。这具有最大并发连接配置值。

您编写了GetConnection和ReturnConnection方法。

GetConnection内部锁定,同时确定现有连接是否可用。然后它将其标记为正在使用并返回它。如果没有可用的连接,则会建立一个新连接(延迟创建),将其标记为正在使用并返回它(假设您处于最大限制之下)。

ReturnConnection内部锁定然后将其标记为可用。如果连接可疑,则返回状态时有一个可选参数(例如,使用时出现套接字错误)。 ReturnConnection做的另一件事是给它加时间戳。如果一段时间没有使用,后台进程会定期关闭超过某个计数的连接。

GetConnection保证事务未启动。如果ReturnConnection在事务中(调用者必须提交,回滚或指示存在问题)或连接已关闭,则抛出异常。