Skip to main content
 首页 » 编程设计

c#之此 Asp.Net WebAPI Controller 方法是否正确地从应用程序池中换出

2024年02月06日32三少

我的 webapi 2 Controller 中有一个 Post 方法,它可以插入数据库,但通常必须重试很多秒才能成功。基本上,这会导致重试之间的大量 sleep 。实际上,它正在运行下面的代码。我的问题是,这段代码是否正确,以便我可以同时运行数千个这样的代码而不用尽我的 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 建议。显示无法将 await 放入 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 添加,尝试企业 block ,缺少类(未找到 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"); 
        } 

和异步插入代码(带有一些...)

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

请您参考如下方法:

没有。至少,您要将 Thread.Sleep 替换为 await Task.Delay。 Thread.Sleep 将阻塞该请求上下文中的线程池线程,不执行任何操作。使用 await 允许该线程返回线程池以用于其他请求。

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

更新:您不能在catch block 中使用await;这是 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"); 
} 

要使用 transient 故障处理应用程序 block ,首先要定义哪些错误是“ transient ”的(应该重试)。根据您的示例代码,您只想在出现 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. 
    } 
}