【问题标题】:threadpool starvation线程池饥饿
【发布时间】:2012-07-26 07:45:17
【问题描述】:

我的问题与 aspx.net 4.0 Web 服务器在负载增加时阻塞有关。通过阻止 我的意思是请求是由客户端发送的,但响应会在大约 45 秒后返回。这在开发和生产环境中是可重现的。这 45 秒似乎是恒定的,我在客户端和调用 Constructor() 和 void Render(HtmlTextWriter writer) 之间的 aspx 页面中测量了这一点。 我在一个页面上使用了几个 SqlDataSource 和自定义控件,总共使用了 6 个 SqlCommand.BeginExecuteReader(...)。如果我使用 BeginExecuteReader / EndExecuteReader 模式停用控件,我可以消除此问题。所以我假设最终其中一个 BeginExecute 调用被阻塞,直到线程池中的线程可用。

我打印调试消息并识别出一种模式,即总是在返回被阻止的请求之前打印一堆线程退出消息:

线程“GetMolFileAsync”(0x1ba4) 已退出,代码为 0 (0x0)。

线程“GetMolFileAsync”(0x27d0) 已退出,代码为 0 (0x0)。

线程 '' (0x23c) 以代码 0 (0x0) 退出。

线程“GetCompoundDepositionInfo”(0x1e88) 已退出,代码为 0 (0x0)。

线程“GetMolFileAsync”(0x2758) 已退出,代码为 0 (0x0)。

0x43 27/07/2012 15:09:42 45 ==> 阻塞线程耗时 45 秒

0x5F 27/07/2012 15:10:27 0 ==> 正常行为,在几毫秒内处理

...

这是向数据库发起请求的方法

public static IAsyncResult GetCompoundDepositionInfoAsync(object sender, EventArgs e, AsyncCallback callback, object state)
    {
        GetCompoundVersionInfoAsyncParameters parameters = (GetCompoundVersionInfoAsyncParameters)state;
        IAsyncResult res = null;

        parameters.cmd = new System.Data.SqlClient.SqlCommand("www.GetCompoundDepositionInfo", new System.Data.SqlClient.SqlConnection(parameters.connectionstring));
        parameters.cmd.CommandType = System.Data.CommandType.StoredProcedure;
        parameters.cmd.Parameters.AddWithValue("@CompoundID", parameters.CompoundID);
        try
        {
            parameters.cmd.Connection.Open();
            res = parameters.cmd.BeginExecuteReader(callback, parameters, System.Data.CommandBehavior.CloseConnection);
        }
        catch (Exception ex)
        {
            if (parameters.cmd.Connection.State == System.Data.ConnectionState.Open)
            {
                parameters.cmd.Connection.Close();
            }
            throw new Exception("Exception in calling GetCompoundDepositionInfoAsync()", ex);
        }
        return res;
    }

这是回调函数

public void GetCompoundDepositionInfoCallback(IAsyncResult result)
    {
        gmdTools.GmdCompound.GetCompoundVersionInfoAsyncParameters param = (gmdTools.GmdCompound.GetCompoundVersionInfoAsyncParameters)result.AsyncState;

        System.Threading.Thread.CurrentThread.Name = "GetCompoundDepositionInfo";
        using(System.Data.SqlClient.SqlCommand command = param.cmd)
        using(System.Data.SqlClient.SqlDataReader reader = command.EndExecuteReader(result))
        {
            try
            {
                if (reader.Read())
                {
                    lblDeposited.Text = string.Concat("at ", reader.GetDateTime(0).ToShortDateString(), " by ", reader.GetString(1));
                }
            }
            finally
            {
                if (reader != null)
                {
                    reader.Close();
                    command.Connection.Close();
                }
            }
        }
    }

这是将它们粘合在一起的代码......

Page.RegisterAsyncTask(new PageAsyncTask(
                new BeginEventHandler(gmdTools.GmdCompound.GetCompoundLastChangeInfoAsync)
                , new EndEventHandler(GetCompoundLastChangeInfoCallback)
                , new EndEventHandler(GetCompoundInfoAsyncTimeout)
                , new gmdTools.GmdCompound.GetCompoundVersionInfoAsyncParameters()
                {
                    connectionstring = Properties.Settings.Default.GmdConnectionString,
                    CompoundID = CompoundId,
                }, true
            ));

由于我已经花费了数小时查看此代码,我将不胜感激任何反馈。

更新 这 45 秒是由默认的 Page.AsyncTimeout 推断的,可以使用 Async="true" AsyncTimeout="10" 语句将其更改为 10 秒。尽管我通过添加适当的索引极大地提高了站点的整体性能,但客户端有时必须等待这段时间,然后服务器才会发送响应。在这种情况下,不会调用 AsyncTimeout 处理程序。我假设页面注册了所有异步操作,但最终无法识别某些异步操作已成功完成,因此在呈现页面之前等待 AsyncTimeout 秒。有什么相关的吗?

【问题讨论】:

  • 看起来像阻塞了数据库。我建议你监控数据库活动/使用 SQL Profiler 来找出数据库上发生了什么。
  • 你确定数据库查询不是这里的罪魁祸首吗?许多客户端的时间一致这一事实表明,共同因素可能是数据库。
  • @ShellShock:感谢您评论我的问题!根据您的经验,您建议 SQL 分析器中的哪些事件来跟踪此阻塞?

标签: c# asp.net sql-server asynchronous sqlcommand


【解决方案1】:

它可能是数据库,考虑到查询返回的选择或行,它选择的选择多于还是少于千分之一? MS SQL将在返回的1000行中通过1以1000行进行更改操作。如果您使用 SQL 探查器运行查询,您会得到表扫描吗?如果您运行内置 sp 来确定缺失的索引,它是否会返回对这些表的索引请求?你的统计数据是最新的吗?对此的线索是恢复的备份会快速运行查询,因为当您恢复备份时,会更新统计信息。您的(所有/每个)表上是否有聚集索引?

这个答案也可能是相关的Entity Framework MVC Slow Page Loads

【讨论】:

  • 我通过添加适当的索引优化了一些查询,从而实现了整体改进。但是,阻塞仍然会发生一些时候。另一方面,我现在使用System.Threading.ThreadPool.GetAvailableThreads(out AvailableWorkerThreads, out AvailablePortThreads)System.Threading.ThreadPool.GetMaxThreads(out MaxWorkerThreads, out MaxPortThreads) 来验证我有足够的2,400 个PortThreads 和2,379 个WorkerThreads 可用。感谢大家让我注意到这一点。
【解决方案2】:

您是否在连接字符串中使用async=true 属性。这是使用 SqlClient 的真正异步操作所必需的。 如果可能的话,您可以在 .Net 4.5 上使用 Task async 功能尝试此操作,代码如下所示。

public async Task GetCompoundDepositionInfoAsync(CancellationToken cancellationToken)
{
    parameters.cmd = new System.Data.SqlClient.SqlCommand("www.GetCompoundDepositionInfo", new System.Data.SqlClient.SqlConnection(parameters.connectionstring));
    parameters.cmd.CommandType = System.Data.CommandType.StoredProcedure;
    parameters.cmd.Parameters.AddWithValue("@CompoundID", parameters.CompoundID);
    using (var connection = new SqlConnection(parameters.connectionstring))
    using (var command = new SqlCommand(query, connection))
    {
        await connection.OpenAsync(cancellationToken);
        using (var reader = await command.ExecuteReaderAsync(cancellationToken))
        {
            if (await reader.ReadAsync(cancellationToken))
            {
                lblDeposited.Text = string.Concat("at ", reader.GetDateTime(0).ToShortDateString(), " by ",     reader.GetString(1));
            }
        }
    }
}

在 page_load() 中

RegisterAsyncTask(new PageAsyncTask(GetCompoundDepositionInfoAsync));

【讨论】:

  • 我确实在连接字符串中使用了async=true
猜你喜欢
  • 2016-06-23
  • 1970-01-01
  • 2021-06-08
  • 2021-02-20
  • 1970-01-01
  • 2020-08-05
  • 2023-01-07
  • 2017-12-15
  • 2021-06-25
相关资源
最近更新 更多