执行块的最大时间.........重试和中间延迟
Maximum Time for an execution block.........with retry and in-between-delay
我正在尝试编写策略。
我希望我的执行代码 运行 持续最长时间(示例中为 10 秒)。
但我也想重试 x 次(示例中为 3 次)。并在失败之间暂停(示例中为 2 秒)。
我设置了我的存储过程来人为地延迟以测试我的行为。
按照编码(代码下方),我的数据集在 30 秒后填充(仅供参考:30 秒是存储过程中的硬编码值)。所以我的执行代码在 10 秒后没有退出....
理想情况下,我看到代码在前两次尝试 10 秒后退出,然后在第三次尝试中继续工作(因为存储过程不会人为延迟)。显然这不是真正的代码,但奇怪的存储过程为我提供了一种测试行为的方法。
我的存储过程:
USE [Northwind]
GO
/* CREATE */ ALTER PROCEDURE [dbo].[uspWaitAndReturn]
(
@InvokeDelay bit
)
AS
SET NOCOUNT ON;
if ( @InvokeDelay > 0)
BEGIN
WAITFOR DELAY '00:00:30';
END
select top 1 * from dbo.Customers c order by newid()
GO
我的 C#/Polly/Database 代码:
public DataSet GetGenericDataSet()
{
DataSet returnDs = null;
int maxRetryAttempts = 3; /* retry attempts */
TimeSpan pauseBetweenFailuresTimeSpan = TimeSpan.FromSeconds(2); /* pause in between failures */
Policy timeoutAfter10SecondsPolicy = Policy.Timeout(TimeSpan.FromSeconds(10)); /* MAGIC SETTING here, my code inside the below .Execute block below would bail out after 10 seconds */
Policy retryThreeTimesWith2SecondsInBetweenPolicy = Policy.Handle<Exception>().WaitAndRetry(maxRetryAttempts, i => pauseBetweenFailuresTimeSpan);
Policy aggregatePolicy = timeoutAfter10SecondsPolicy.Wrap(retryThreeTimesWith2SecondsInBetweenPolicy);
int attemptCounter = 0; /* used to track the attempt and conditionally set the @InvokeDelay value for the stored procedure */
aggregatePolicy.Execute(() =>
{
try
{
attemptCounter++;
/* Microsoft.Practices.EnterpriseLibrary.Data code */
////////DatabaseProviderFactory factory = new DatabaseProviderFactory();
////////Database db = factory.CreateDefault();
////////DbCommand dbc = db.GetStoredProcCommand("dbo.uspWaitAndReturn");
////////dbc.CommandTimeout = 120;
////////db.AddInParameter(dbc, "@InvokeDelay", DbType.Boolean, attemptCounter < maxRetryAttempts ? true : false); /* if i'm not on my last attempt, then pass in true to cause the artificial delay */
////////DataSet ds;
////////ds = db.ExecuteDataSet(dbc);
////////returnDs = ds;
using (SqlConnection conn = new SqlConnection(@"MyConnectionStringValueHere"))
{
SqlCommand sqlComm = new SqlCommand("[dbo].[uspWaitAndReturn]", conn);
sqlComm.Parameters.AddWithValue("@InvokeDelay", attemptCounter < maxRetryAttempts ? true : false);
sqlComm.CommandType = CommandType.StoredProcedure;
SqlDataAdapter da = new SqlDataAdapter();
da.SelectCommand = sqlComm;
DataSet ds = new DataSet();
da.Fill(ds);
returnDs = ds;
}
}
catch (Exception ex)
{
string temp = ex.Message;
throw;
}
});
return returnDs;
}
using语句:
using System;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
//// using Microsoft.Practices.EnterpriseLibrary.Data;
using Polly;
版本 (packages.config)
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="CommonServiceLocator" version="1.0" targetFramework="net40" />
<package id="EnterpriseLibrary.Common" version="6.0.1304.0" targetFramework="net45" />
<package id="EnterpriseLibrary.Data" version="6.0.1304.0" targetFramework="net45" />
<package id="Polly" version="5.6.1" targetFramework="net45" />
/>
</packages>
追加:
经过@mountain traveler 的精彩回答,我有一个工作示例:
重点是:
已添加TimeoutStrategy.Pessimistic
并添加了一个 DbCommand.Cancel() 调用(或者 SqlCommand.Cancel() 如果你不使用企业库)来终止(之前的)命令,否则它们将继续 运行(不好)。
我还必须 "reverse" 我的 "Policy aggregatePolicy"。
public DataSet GetGenericDataSet()
{
DataSet returnDs = null;
DbCommand dbc = null; /* increase scope so it can be cancelled */
int maxRetryAttempts = 3; /* retry attempts */
TimeSpan pauseBetweenFailuresTimeSpan = TimeSpan.FromSeconds(2); /* pause in between failures */
Policy timeoutAfter10SecondsPolicy = Policy.Timeout(
TimeSpan.FromSeconds(10),
TimeoutStrategy.Pessimistic,
(context, timespan, task) =>
{
string x = timespan.Seconds.ToString();
if (null != dbc)
{
dbc.Cancel();
dbc = null;
}
});
Policy retryThreeTimesWith2SecondsInBetweenPolicy = Policy.Handle<Exception>().WaitAndRetry(maxRetryAttempts, i => pauseBetweenFailuresTimeSpan);
////Policy aggregatePolicy = timeoutAfter10SecondsPolicy.Wrap(retryThreeTimesWith2SecondsInBetweenPolicy);
Policy aggregatePolicy = retryThreeTimesWith2SecondsInBetweenPolicy.Wrap(timeoutAfter10SecondsPolicy);
int attemptCounter = 0; /* used to track the attempt and conditionally set the @InvokeDelay value for the stored procedure */
aggregatePolicy.Execute(() =>
{
try
{
attemptCounter++;
/* Microsoft.Practices.EnterpriseLibrary.Data code */
DatabaseProviderFactory factory = new DatabaseProviderFactory();
Database db = factory.CreateDefault();
dbc = db.GetStoredProcCommand("dbo.uspWaitAndReturn");
dbc.CommandTimeout = 120;
db.AddInParameter(dbc, "@InvokeDelay", DbType.Boolean, attemptCounter < maxRetryAttempts ? true : false); /* if i'm not on my last attempt, then pass in true to cause the artificial delay */
DataSet ds;
ds = db.ExecuteDataSet(dbc);
returnDs = ds;
////////using (SqlConnection conn = new SqlConnection(@"YOUR_VALUE_HERE"))
////////{
//////// SqlCommand sqlComm = new SqlCommand("[dbo].[uspWaitAndReturn]", conn);
//////// sqlComm.Parameters.AddWithValue("@InvokeDelay", attemptCounter < maxRetryAttempts ? true : false);
//////// sqlComm.CommandType = CommandType.StoredProcedure;
//////// SqlDataAdapter da = new SqlDataAdapter();
//////// da.SelectCommand = sqlComm;
//////// DataSet ds = new DataSet();
//////// da.Fill(ds);
//////// returnDs = ds;
////////}
}
catch (SqlException sqlex)
{
switch (sqlex.ErrorCode)
{
case -2146232060:
/* I couldn't find a more concrete way to find this specific exception, -2146232060 seems to represent alot of things */
if (!sqlex.Message.Contains("cancelled"))
{
throw;
}
break;
default:
throw;
}
}
});
return returnDs;
}
波利 TimeoutPolicy
comes in two modes:
Optimistic Timeout enforces the timeout by co-operative cancellation 通过 CancellationToken
。它期望被执行的代表响应 CancellationToken
。
Pessimistic Timeout 对不响应CancellationToken
的委托强制超时
您正在执行的委托不接受任何 CancellationToken
。因此(对于发布的原始代码)您需要配置策略以使用 TimeoutStrategy.Pessimistic
:
Policy timeoutAfter10SecondsPolicy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic);
(在发布的原始代码中,Policy timeoutAfter10SecondsPolicy = Policy.Timeout(TimeSpan.FromSeconds(10));
采用了 TimeoutStrategy.Optimistic
,因为这是默认值。)
以上是为了解释为什么您没有看到 TimeoutPolicy
在提供的代码中工作。 也就是说:注意discussion in the Polly wiki about what pessimistic timeout means:它允许调用线程离开等待一个已执行的委托,但不会取消该委托的线程/Task
运行。所以正在使用的 SQL 资源不会在超时时释放。
为确保 SQL 资源在超时时被释放,您需要扩展代码以在超时时取消 SQL 操作。您可以使用在超时发生时调用的 SqlCommand.Cancel() method, for example as shown in this Whosebug answer. Polly's TimeoutPolicy
can be configured with an onTimeout
delegate:即将此委托配置为调用适当的 SqlCommand.Cancel()
.
或者,可以将整个代码移动到异步方法,并使用由 CancellationToken
驱动的 SqlCommand.ExecuteReaderAsync(CancellationToken), coupled with Polly's optimistic timeout 之类的东西。但这是一个更广泛的讨论。
我正在尝试编写策略。
我希望我的执行代码 运行 持续最长时间(示例中为 10 秒)。 但我也想重试 x 次(示例中为 3 次)。并在失败之间暂停(示例中为 2 秒)。
我设置了我的存储过程来人为地延迟以测试我的行为。
按照编码(代码下方),我的数据集在 30 秒后填充(仅供参考:30 秒是存储过程中的硬编码值)。所以我的执行代码在 10 秒后没有退出....
理想情况下,我看到代码在前两次尝试 10 秒后退出,然后在第三次尝试中继续工作(因为存储过程不会人为延迟)。显然这不是真正的代码,但奇怪的存储过程为我提供了一种测试行为的方法。
我的存储过程:
USE [Northwind]
GO
/* CREATE */ ALTER PROCEDURE [dbo].[uspWaitAndReturn]
(
@InvokeDelay bit
)
AS
SET NOCOUNT ON;
if ( @InvokeDelay > 0)
BEGIN
WAITFOR DELAY '00:00:30';
END
select top 1 * from dbo.Customers c order by newid()
GO
我的 C#/Polly/Database 代码:
public DataSet GetGenericDataSet()
{
DataSet returnDs = null;
int maxRetryAttempts = 3; /* retry attempts */
TimeSpan pauseBetweenFailuresTimeSpan = TimeSpan.FromSeconds(2); /* pause in between failures */
Policy timeoutAfter10SecondsPolicy = Policy.Timeout(TimeSpan.FromSeconds(10)); /* MAGIC SETTING here, my code inside the below .Execute block below would bail out after 10 seconds */
Policy retryThreeTimesWith2SecondsInBetweenPolicy = Policy.Handle<Exception>().WaitAndRetry(maxRetryAttempts, i => pauseBetweenFailuresTimeSpan);
Policy aggregatePolicy = timeoutAfter10SecondsPolicy.Wrap(retryThreeTimesWith2SecondsInBetweenPolicy);
int attemptCounter = 0; /* used to track the attempt and conditionally set the @InvokeDelay value for the stored procedure */
aggregatePolicy.Execute(() =>
{
try
{
attemptCounter++;
/* Microsoft.Practices.EnterpriseLibrary.Data code */
////////DatabaseProviderFactory factory = new DatabaseProviderFactory();
////////Database db = factory.CreateDefault();
////////DbCommand dbc = db.GetStoredProcCommand("dbo.uspWaitAndReturn");
////////dbc.CommandTimeout = 120;
////////db.AddInParameter(dbc, "@InvokeDelay", DbType.Boolean, attemptCounter < maxRetryAttempts ? true : false); /* if i'm not on my last attempt, then pass in true to cause the artificial delay */
////////DataSet ds;
////////ds = db.ExecuteDataSet(dbc);
////////returnDs = ds;
using (SqlConnection conn = new SqlConnection(@"MyConnectionStringValueHere"))
{
SqlCommand sqlComm = new SqlCommand("[dbo].[uspWaitAndReturn]", conn);
sqlComm.Parameters.AddWithValue("@InvokeDelay", attemptCounter < maxRetryAttempts ? true : false);
sqlComm.CommandType = CommandType.StoredProcedure;
SqlDataAdapter da = new SqlDataAdapter();
da.SelectCommand = sqlComm;
DataSet ds = new DataSet();
da.Fill(ds);
returnDs = ds;
}
}
catch (Exception ex)
{
string temp = ex.Message;
throw;
}
});
return returnDs;
}
using语句:
using System;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
//// using Microsoft.Practices.EnterpriseLibrary.Data;
using Polly;
版本 (packages.config)
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="CommonServiceLocator" version="1.0" targetFramework="net40" />
<package id="EnterpriseLibrary.Common" version="6.0.1304.0" targetFramework="net45" />
<package id="EnterpriseLibrary.Data" version="6.0.1304.0" targetFramework="net45" />
<package id="Polly" version="5.6.1" targetFramework="net45" />
/>
</packages>
追加:
经过@mountain traveler 的精彩回答,我有一个工作示例:
重点是:
已添加TimeoutStrategy.Pessimistic
并添加了一个 DbCommand.Cancel() 调用(或者 SqlCommand.Cancel() 如果你不使用企业库)来终止(之前的)命令,否则它们将继续 运行(不好)。
我还必须 "reverse" 我的 "Policy aggregatePolicy"。
public DataSet GetGenericDataSet()
{
DataSet returnDs = null;
DbCommand dbc = null; /* increase scope so it can be cancelled */
int maxRetryAttempts = 3; /* retry attempts */
TimeSpan pauseBetweenFailuresTimeSpan = TimeSpan.FromSeconds(2); /* pause in between failures */
Policy timeoutAfter10SecondsPolicy = Policy.Timeout(
TimeSpan.FromSeconds(10),
TimeoutStrategy.Pessimistic,
(context, timespan, task) =>
{
string x = timespan.Seconds.ToString();
if (null != dbc)
{
dbc.Cancel();
dbc = null;
}
});
Policy retryThreeTimesWith2SecondsInBetweenPolicy = Policy.Handle<Exception>().WaitAndRetry(maxRetryAttempts, i => pauseBetweenFailuresTimeSpan);
////Policy aggregatePolicy = timeoutAfter10SecondsPolicy.Wrap(retryThreeTimesWith2SecondsInBetweenPolicy);
Policy aggregatePolicy = retryThreeTimesWith2SecondsInBetweenPolicy.Wrap(timeoutAfter10SecondsPolicy);
int attemptCounter = 0; /* used to track the attempt and conditionally set the @InvokeDelay value for the stored procedure */
aggregatePolicy.Execute(() =>
{
try
{
attemptCounter++;
/* Microsoft.Practices.EnterpriseLibrary.Data code */
DatabaseProviderFactory factory = new DatabaseProviderFactory();
Database db = factory.CreateDefault();
dbc = db.GetStoredProcCommand("dbo.uspWaitAndReturn");
dbc.CommandTimeout = 120;
db.AddInParameter(dbc, "@InvokeDelay", DbType.Boolean, attemptCounter < maxRetryAttempts ? true : false); /* if i'm not on my last attempt, then pass in true to cause the artificial delay */
DataSet ds;
ds = db.ExecuteDataSet(dbc);
returnDs = ds;
////////using (SqlConnection conn = new SqlConnection(@"YOUR_VALUE_HERE"))
////////{
//////// SqlCommand sqlComm = new SqlCommand("[dbo].[uspWaitAndReturn]", conn);
//////// sqlComm.Parameters.AddWithValue("@InvokeDelay", attemptCounter < maxRetryAttempts ? true : false);
//////// sqlComm.CommandType = CommandType.StoredProcedure;
//////// SqlDataAdapter da = new SqlDataAdapter();
//////// da.SelectCommand = sqlComm;
//////// DataSet ds = new DataSet();
//////// da.Fill(ds);
//////// returnDs = ds;
////////}
}
catch (SqlException sqlex)
{
switch (sqlex.ErrorCode)
{
case -2146232060:
/* I couldn't find a more concrete way to find this specific exception, -2146232060 seems to represent alot of things */
if (!sqlex.Message.Contains("cancelled"))
{
throw;
}
break;
default:
throw;
}
}
});
return returnDs;
}
波利 TimeoutPolicy
comes in two modes:
Optimistic Timeout enforces the timeout by co-operative cancellation 通过
CancellationToken
。它期望被执行的代表响应CancellationToken
。Pessimistic Timeout 对不响应
CancellationToken
的委托强制超时
您正在执行的委托不接受任何 CancellationToken
。因此(对于发布的原始代码)您需要配置策略以使用 TimeoutStrategy.Pessimistic
:
Policy timeoutAfter10SecondsPolicy = Policy.Timeout(TimeSpan.FromSeconds(10), TimeoutStrategy.Pessimistic);
(在发布的原始代码中,Policy timeoutAfter10SecondsPolicy = Policy.Timeout(TimeSpan.FromSeconds(10));
采用了 TimeoutStrategy.Optimistic
,因为这是默认值。)
以上是为了解释为什么您没有看到 TimeoutPolicy
在提供的代码中工作。 也就是说:注意discussion in the Polly wiki about what pessimistic timeout means:它允许调用线程离开等待一个已执行的委托,但不会取消该委托的线程/Task
运行。所以正在使用的 SQL 资源不会在超时时释放。
为确保 SQL 资源在超时时被释放,您需要扩展代码以在超时时取消 SQL 操作。您可以使用在超时发生时调用的 SqlCommand.Cancel() method, for example as shown in this Whosebug answer. Polly's TimeoutPolicy
can be configured with an onTimeout
delegate:即将此委托配置为调用适当的 SqlCommand.Cancel()
.
或者,可以将整个代码移动到异步方法,并使用由 CancellationToken
驱动的 SqlCommand.ExecuteReaderAsync(CancellationToken), coupled with Polly's optimistic timeout 之类的东西。但这是一个更广泛的讨论。