线程饥饿

时间:2012-07-27 15:33:00

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

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

我打印调试消息并识别出一种模式,其中只有在被阻止的请求被返回时才打印出一堆线程退出消息:

  

线程'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秒。对此有何评论?

2 个答案:

答案 0 :(得分:0)

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

此答案可能与Entity Framework MVC Slow Page Loads

相关

答案 1 :(得分:0)

您是否在connectionstring中使用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));