此Asp.Net WebAPI控制器方法是否正确地从应用程序池中换出?

时间:2014-08-21 12:56:41

标签: c# asp.net-web-api async-await asp.net-web-api2 c#-5.0

我的webapi 2控制器中有一个Post方法,可以插入数据库,但通常需要在成功之前重试几秒钟。基本上,这会导致重试之间的大量睡眠。实际上,它正在运行下面的代码。我的问题是,这段代码是否正确,以便我可以同时运行数千个这样的代码并且不会耗尽我的iis页面池?

   public async Task<HttpResponseMessage> Post()
    {


        try
        {
            HttpContent requestContent = Request.Content;
            string json = await requestContent.ReadAsStringAsync();

            Thread.Sleep(30000);
            //InsertInTable(json);

        }
        catch (Exception ex)
        {
            throw ex;
        }

        return new HttpResponseMessage(HttpStatusCode.OK);

    }

* 由Peter添加为试试Stephens对Await.Delay的建议。显示无法在catch中等待的错误。

    public async Task<HttpResponseMessage> PostXXX()
    {
        HttpContent requestContent = Request.Content;
        string json = await requestContent.ReadAsStringAsync();

        bool success = false;
        int retriesMax = 30;
        int retries = retriesMax;
        while (retries > 0)
        {
            try
            {
                // DO ADO.NET EXECUTE THAT MAY FAIL AND NEED RETRY
                retries = 0;
            }
            catch (SqlException exception)
            {
                // exception is a deadlock
                if (exception.Number == 1205)
                {
                    await Task.Delay(1000);
                    retries--;
                }
                    // exception is not a deadlock
                else
                {
                    throw;
                }
            }
        }
        return new HttpResponseMessage(HttpStatusCode.OK);
    }

* 更多由Peter提供,试用Enterprise块,缺少类(未找到StorageTransientErrorDetectionStrategy类)

public async Task<HttpResponseMessage> Post()
    {
        HttpContent requestContent = Request.Content;
        string json = await requestContent.ReadAsStringAsync();

        var retryStrategy = new Incremental(5, TimeSpan.FromSeconds(1),
            TimeSpan.FromSeconds(2));
        var retryPolicy =
          new RetryPolicy<StorageTransientErrorDetectionStrategy>(retryStrategy);

        try
        {
            // Do some work that may result in a transient fault.
            retryPolicy.ExecuteAction(
              () =>
              {
                  // Call a method that uses Windows Azure storage and which may
                  // throw a transient exception.
                  Thread.Sleep(10000);
              });
        }
        catch (Exception)
        {
            // All the retries failed.
        }

*****导致SqlServer失控的开启连接的代码

          try
          {
            await retryPolicy.ExecuteAsync(
                async () =>
                {
                    // this opens a SqlServer Connection and Transaction.
                    // if fails, rolls back and rethrows exception so
                    // if deadlock, this retry loop will handle correctly
                    // (caused sqlserver to spin out of control with open
                    //  connections so replacing with simple call and
                    //  letting sendgrid retry non 200 returns)
                    await InsertBatchWithTransaction(sendGridRecordList);
                });
        }
        catch (Exception)
        {
            Utils.Log4NetSimple("SendGridController:POST Retries all failed");
        }

和异步插入代码(有一些......&#39; s)

   private static async Task
         InsertBatchWithTransaction(List<SendGridRecord> sendGridRecordList)
    {
        using (
            var sqlConnection =
                  new SqlConnection(...))
        {
            await sqlConnection.OpenAsync();
            const string sqlInsert =
                @"INSERT INTO  SendGridEvent...

            using (SqlTransaction transaction = 
                  sqlConnection.BeginTransaction("SampleTransaction"))
            {
                using (var sqlCommand = new SqlCommand(sqlInsert, sqlConnection))
                {
                    sqlCommand.Parameters.Add("EventName", SqlDbType.VarChar);

                    sqlCommand.Transaction = transaction;

                    try
                    {
                        foreach (var sendGridRecord in sendGridRecordList)
                        {
                            sqlCommand.Parameters["EventName"].Value = 
                                GetWithMaxLen(sendGridRecord.EventName, 60);

                            await sqlCommand.ExecuteNonQueryAsync();
                        }
                        transaction.Commit();
                    }
                    catch (Exception)
                    {
                        transaction.Rollback();
                        throw;
                    }
                }
            }
        }
    }

1 个答案:

答案 0 :(得分:4)

没有。至少,您希望将Thread.Sleep替换为await Task.DelayThread.Sleep将阻止该请求上下文中的线程池线程,什么都不做。使用await允许该线程返回到线程池以用于其他请求。

您可能还想考虑Transient Fault Handling Application Block

更新:您无法在await块中使用catch;这是VS2013中C#语言的限制(下一个版本可能允许这样,as I note on my blog)。所以现在,你必须做这样的事情:

private async Task RetryAsync(Func<Task> action, int retries = 30)
{
  while (retries > 0)
  {
    try
    {
      await action();
      return;
    }
    catch (SqlException exception)
    {
      // exception is not a deadlock
      if (exception.Number != 1205)
        throw;
    }
    await Task.Delay(1000);
    retries--;
  }

  throw new Exception("Retry count exceeded");
}

要使用瞬态故障处理应用程序块,首先要定义哪些错误是&#34;瞬态&#34; (应该重试)。根据您的示例代码,您只想在出现SQL死锁异常时重试:

private sealed class DatabaseDeadlockTransientErrorDetectionStrategy : ITransientErrorDetectionStrategy
{
    public bool IsTransient(Exception ex)
    {
        var sqlException = ex as SqlException;
        if (sqlException == null)
            return false;
        return sqlException.Number == 1205;
    }

    public static readonly DatabaseDeadlockTransientErrorDetectionStrategy Instance = new DatabaseDeadlockTransientErrorDetectionStrategy();
}

然后你可以这样使用它:

private static async Task RetryAsync()
{
    var retryStrategy = new Incremental(5, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2));
    var retryPolicy = new RetryPolicy(DatabaseDeadlockTransientErrorDetectionStrategy.Instance, retryStrategy);

    try
    {
        // Do some work that may result in a transient fault.
        await retryPolicy.ExecuteAsync(
            async () =>
            {
                // TODO: replace with async ADO.NET calls.
                await Task.Delay(1000);
            });
    }
    catch (Exception)
    {
        // All the retries failed.
    }
}