简体   繁体   English

多线程环境中的Firebird

[英]Firebird in multi-thread environment

I have created a singleton class for holding the Firebird database connection and I'm executing queries (see below), which worked like a charm, until I had to add to one of the applications some timers to set up & check some data regularly. 我已经创建了一个用于容纳Firebird数据库连接的单例类,并且正在执行查询(请参见下文),该查询的工作原理非常吸引人,直到我不得不向其中一个应用程序添加一些计时器来定期设置和检查某些数据。

Now very often I get error saying that "Parallel transactions are not supported". 现在,我经常出错,说“不支持并行事务”。 I can't get it, because I got closing of the connection in finally block. 我不明白,因为我在finally块中关闭了连接。

I already spent few very long evenings on searching the web and trying to figure out how to approach, but it seems I need support. 我已经花了很长时间来搜索网络并试图找出解决方法,但似乎我需要支持。

Here is the main part of my fBird class: 这是我的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();
            }
        }
    }
}

Here is how I use the class: 这是我使用课程的方式:

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

Here is how I use timers: 这是计时器的使用方法:

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

What I have found out is that for some reason there is more than one connection to Firebird made (singleton shouldn't allow that, right?). 我发现,由于某种原因,与Firebird的连接不止一个(单个子不应该允许,对吧?)。 I can see it in Windows Performance Monitor on Network tab. 我可以在Windows性能监视器的“网络”选项卡上看到它。 As a testing I do very different operations, open different windows & screens in application, also save some changes. 作为测试,我执行非常不同的操作,在应用程序中打开不同的窗口和屏幕,还保存了一些更改。 At random I got error (parallel transactions) and new connection is created and than for some time (like 30-40 clicks) all works fine. 随机出现错误(并行事务),并创建了新的连接,并且一段时间后(例如30-40次单击),一切正常。

It just drives me crazy. 这简直让我发疯。 I hope someone of you will be able to help me. 我希望你们中的某人能够帮助我。 I have feeling that I do something really wrong here, but I just can't find it. 我感觉自己在这里做错了什么,但是我找不到。 Let me know if you need any more information. 让我知道您是否需要更多信息。

If you are using multiple threads, then you should not share a single connection. 如果你是使用多线程,那么你应该共享一个连接。 Your problem is not one of multiple connections, but of multiple threads using the same connection, and trying to create their own transactions, which is explicitly not supported by the driver. 您的问题不是多个连接之一,而是多个使用同一连接并尝试创建自己的事务的线程,而驱动程序显然不支持这些事务。 Your code is likely to suffer from other race conditions as well. 您的代码也可能会遭受其他竞争条件的困扰。

Stop using that singleton. 停止使用该单例。 Instead obtain a new connection for each unit of work and close it when you're done with it. 而是为每个工作单元获取一个新的连接,并在完成后关闭它。 The Firebird ADO.net provider by defaults provides connection pooling, so it is relatively cheap to do. 默认情况下,Firebird ADO.net提供程序提供连接池,因此这样做相对便宜。

Personally, I would get rid entirely of that fBird class, but otherwise at minimum get rid of the shared instance, implement IDisposable to close the connection on dispose, add a static method to create instances on demand. 就个人而言,我将完全摆脱该fBird类,但否则至少摆脱该共享实例,实现IDisposable以便在处置时关闭连接,添加静态方法以根据需要创建实例。 You should then use those fBird instances in a using to make sure it gets correctly closed at the end. 然后,应在using使用这些fBird实例,以确保最后正确关闭它。

The reason why I suggest to get rid of that fBird class, is that you are just wrapping the normal connection object, and providing a poorer and more brittle abstraction. 我建议摆脱该fBird类的原因是,您只是包装了普通的连接对象,并提供了较差且较脆的抽象。

您需要使用锁,单例不能保证单个用户

There are three things about your fBird class that you may want to rethink. 关于fBird类,您可能需要重新考虑三件事。

Firstly, as other answers have pointed out, the FBConnection object is not threadsafe. 首先,正如其他答案所指出的那样,FBConnection对象不是线程安全的。 (That's a Firebird design/restriction, not a C# library specific behaviour). (这是Firebird的设计/限制,而不是C#库特定的行为)。 That means that you may either: 这意味着您可以:

  • Ensure that each of your threads gets their own instance of this class; 确保您的每个线程都拥有自己的此类的实例; OR 要么
  • Ensure that access to your single connection is serialised (ie use a lock()) 确保对您的单个连接的访问​​已序列化(即使用lock())

Secondly, your methods are very heavy as you are doing a full connect/disconnect each time. 其次,您的方法非常繁琐,因为您每次都要进行完全连接/断开连接。 That will really slow things down when you turn up the volume. 当您调高音量时,这确实会减慢速度。

Thirdly, you are always committing in NonQuery. 第三,您始终在NonQuery中进行提交。 If your operation involves more than one query then your database may be left inconsistent if there is a failure between the two. 如果您的操作涉及多个查询,那么如果两者之间都失败,则数据库可能会不一致。

Here is an idea as a compromise solution for my first two points. 这是我的前两点的折衷解决方案。 In one of my old applications (not c# sorry), I created a connection broker to reduce the connect/disconnect overhead in a restful service. 在我的一个旧应用程序中(不对不起C#),我创建了一个连接代理,以减少静态服务中的连接/断开连接开销。 This had a maximum concurrent connections configuration value. 这具有最大的并发连接配置值。

You write a GetConnection and ReturnConnection method. 您编写一个GetConnection和ReturnConnection方法。

The GetConnection internally locks while it determines whether an existing connection is available. GetConnection在确定现有连接是否可用时在内部锁定。 It then flags it as in use and returns it. 然后将其标记为正在使用并返回。 Where no connection is available, it then establishes a new one (lazy create), flags it as in use and returns it (providing you are under the maximum limit). 如果没有可用的连接,则它将建立一个新连接(延迟创建),将其标记为已使用并返回(前提是您处于最大限制之内)。

The ReturnConnection internally locks then flags it as available. ReturnConnection内部锁定,然后将其标记为可用。 There is an optional parameter on return to state if the connection is suspect (eg you had a socket error when using it). 如果可疑连接(例如,使用连接时发生套接字错误),则在返回状态时有一个可选参数。 The other thing ReturnConnection does is to timestamp it. ReturnConnection要做的另一件事是给它加时间戳。 A background process periodically closes connections above a certain count if they haven't been used for a while. 如果一段时间未使用连接,则后台进程会定期关闭超过一定数量的连接。

GetConnection guarantees the transaction is not started. GetConnection保证事务未启动。 ReturnConnection throws an exception if it is in transaction (caller must commit, rollback, or indicate there was a problem) or if the connection has been closed. 如果它正在事务中(调用者必须提交,回滚或指示存在问题),或者连接已关闭,则ReturnConnection引发异常。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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