【问题标题】:Object pooling in C# throwing StackoverFlow ExceptionC# 中的对象池抛出 StackoverFlow 异常
【发布时间】:2017-01-21 16:26:21
【问题描述】:

我正在尝试在 C# 中实现对象池。我的要求是有一个可以容纳 100 个活动 SqlConnection 对象的池。如果连接池已经有 100 个连接,并且如果用户请求新连接,则连接池必须等待一个现有连接释放。

下面是我使用的代码。池达到 100 个正在使用的对象后,我收到 StackOverFlow 异常。

请在下面的代码中提出 StackOverFlow 异常的可能原因。

class ObjectPoolingTest
{
    static void Main(string[] args)
    {

        int insertedRecords = 1;
        Parallel.For(1, 150000, i =>
        {
            test1(i);
            Console.WriteLine("{0} - Query executed", insertedRecords);
            insertedRecords++;
        }
        );
        Console.ReadKey();

    }

    static void test1(int hitNo)
    {
        var objConnection = Pool.GetConnection();
        SqlCommand cmd = new SqlCommand();
        cmd.Connection = objConnection.ConnectionObject;
        cmd.CommandType = CommandType.StoredProcedure;
        cmd.CommandText = "[dbo].[prSaveRecNumber]";
        cmd.CommandTimeout = 30;
        cmd.Parameters.Add("@recNo", SqlDbType.Int).Value = hitNo;
        int result = cmd.ExecuteNonQuery();
        Pool.ReleaseConnection(objConnection);
        Console.WriteLine(Pool.Message);
    }
}

public class Pool
{
    private static List<Connection> _available = new List<Connection>();
    private static List<Connection> _inUse = new List<Connection>();
    private static int MaxPoolSize = 100;

    private static object lock1 = new Object();
    private static object lock2 = new Object();

    public static string Message
    {
        get
        {
            return string.Format("Available: {0} - InUse: {1}", _available.Count, _inUse.Count);
        }
    }

    public static Connection GetConnection()
    {
        lock (lock1)
        {
            if (_available.Count != 0)
            {
                Connection connection = _available[0];
                _inUse.Add(connection);
                _available.RemoveAt(0);
                return connection;
            }
            else if ((_available.Count + _inUse.Count) != MaxPoolSize)
            {
                Connection connection = new Connection();
                connection.ConnectionObject = new SqlConnection("Server= abcd; Database=sai; User Id=sa; Password=abcd;");

                connection.ConnectionObject.Open();
                _inUse.Add(connection);
                return connection;
            }
            return GetConnection();
        }
    }
    public static void ReleaseConnection(Connection connection)
    {
        lock (lock1)
        {
            _available.Add(connection);
            _inUse.Remove(connection);
        }
    }
}

【问题讨论】:

  • 我建议你学习如何使用调试器。它会快速向您显示代码中发生了什么,您可以快速修复它。另外既然SqlConnection内部已经有pooling了,为什么还要自己实现呢?
  • 这与调试器@SamiKuhmonen 无关。当您使用递归时,您无法获得准确的结果。出于一些明显的原因,我想实现自己的池化。感谢您的答复。 John Koerner 解决方案解决了问题
  • 您可以得到确切的结果,因为您捕获了异常并查看导致它的原因。这就是调试器的用途。正如我所说,它会立即显示无限递归问题。

标签: c# multithreading locking stack-overflow object-pooling


【解决方案1】:

当您达到最大值 100 时,您正在递归调用 GetConnection。由于您正在并行执行 150000,一旦您使用中达到 100(在这种情况下会很快发生),您将开始递归调用并最终点击StackOverflowException,因为递归调用的发生速度比被释放的连接快得多,而且发生的调用比可用的连接多得多。

如果期望的行为是等待一定时间后重试,则需要将 GetConnection 调用重构为无限循环,不断调用相同的代码,直到获得连接。

您可能还想考虑添加一个超时值,如果您在某个时间段内无法获得连接,则抛出异常。

最后,如果您认为有可能存在可用连接,您可能只想输入锁定的代码。这会导致对锁进行双重检查,但您会减少输入锁定代码的频率。

【讨论】:

  • 感谢您的解决方案。删除递归方法调用解决了问题
猜你喜欢
  • 2022-07-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-06-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多