使用 Polly 反对 Task.TimeoutAfter
Using Polly in oppose to Task.TimeoutAfter
如何使用 Polly 来对抗 TimeoutAfter
?
我想在下面的代码中使用 Polly 而不是 TimeoutAfter
:
public async Task StartAsync()
{
await _webSocket.ConnectAsync(_uri, CancellationToken.None).TimeoutAfter(OpenTimeoutMs).ConfigureAwait(false);
...
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None).TimeoutAfter(CloseTimeoutMs);
}
public static async Task TimeoutAfter(this Task task, TimeSpan timeout)
{
try
{
await task.WaitAsync(timeout).ConfigureAwait(false);
}
catch (TimeoutException ex)
{
throw new TimeoutException($"Task timed out after {timeout}");
}
}
我的想法(不确定我是否正确)
为了让 Polly 处理事情,我需要将 CancellationToken
传递给 StartAsync
并用它替换 CancellationToken.None
。
我是这么想的,不知道对不对。这就是我问这个问题的原因。
var timeoutPolicy = Policy
.Handle<TimeoutException>()
.TimeoutAsync(TimeSpan.FromMilliseconds(timeoutMs), TimeoutStrategy.Optimistic,
(context, timeSpan, task, ex) =>
{
Console.WriteLine($"Task timed out after {timeSpan.TotalSeconds} seconds");
return Task.CompletedTask;
});
await retryPolicy.ExecuteAsync(async (ct) =>
{
await client.StartAsync(ct);
}, CancellationToken.None);
// changed the method to accept CancellationToken
public async Task StartAsync(CancellationToken ct = default)
{
await _webSocket.ConnectAsync(_uri, ct).ConfigureAwait(false);
...
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", ct).ConfigureAwait(false);
}
您的 TimeoutAfter
扩展方法被应用了两次,分别用于 ConnectAsync
和 CloseAsync
方法。这意味着每个方法调用都有一个“本地”超时。
在您的 Polly 版本中,您定义了一个“全局”/“总体”超时,其中包括从 ConnectAsync
到 CloseAsync
的所有内容。
因此,这两个版本不相同。
顺便说一句,你的代码有一些问题:
TimeoutAsync
不能有 Handle
子句
retryPolicy
未在此范围内定义
您可以将 TimeoutAfter
代码替换为此代码以利用 Polly:
public static async Task TimeoutAfter(Func<CancellationToken, Task> task, CancellationToken token, TimeSpan timeout)
{
try
{
await CreateTimeoutConstraint(timeout)
.ExecuteAsync(async (ct) => await task(ct), token)
.ConfigureAwait(false);
}
catch (TimeoutRejectedException ex)
{
throw new OperationCanceledException($"Task timed out after {timeout}", ex);
}
}
private static IAsyncPolicy CreateTimeoutConstraint(TimeSpan threshold)
=> Policy.TimeoutAsync(threshold, TimeoutStrategy.Optimistic,
(context, timeSpan, task, ex) =>
{
Console.WriteLine($"Task timed out after {timeSpan.TotalSeconds} seconds");
return Task.CompletedTask;
});
- 为了尊重用户取消令牌和超时取消令牌,您必须更改签名
- 该方法应该接收一个函数,该函数预期
CancellationToken
和 returns 一个 Task
- 它还应该收到用户取消令牌
- 在
ExecuteAsync
中调用您“组合”TimeoutPolicy
和用户取消令牌
- 如果
token
先请求取消,那么 ExecuteAsync
将抛出一个 TaskCanceledException
- 如果 TimeoutPolicy 首先请求取消,那么
ExecuteAsync
将抛出一个 TimeoutRejectedException
,它被转换为一个 OperationCanceledException
这个方法的用法是这样的:
await TimeoutAfter((token) => _webSocket.ConnectAsync(_uri, token), ct, OpenTimeoutMs);
...
await TimeoutAfter((token) => _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", token), ct, CloseTimeoutMs);
如何使用 Polly 来对抗 TimeoutAfter
?
我想在下面的代码中使用 Polly 而不是 TimeoutAfter
:
public async Task StartAsync()
{
await _webSocket.ConnectAsync(_uri, CancellationToken.None).TimeoutAfter(OpenTimeoutMs).ConfigureAwait(false);
...
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None).TimeoutAfter(CloseTimeoutMs);
}
public static async Task TimeoutAfter(this Task task, TimeSpan timeout)
{
try
{
await task.WaitAsync(timeout).ConfigureAwait(false);
}
catch (TimeoutException ex)
{
throw new TimeoutException($"Task timed out after {timeout}");
}
}
我的想法(不确定我是否正确)
为了让 Polly 处理事情,我需要将 CancellationToken
传递给 StartAsync
并用它替换 CancellationToken.None
。
我是这么想的,不知道对不对。这就是我问这个问题的原因。
var timeoutPolicy = Policy
.Handle<TimeoutException>()
.TimeoutAsync(TimeSpan.FromMilliseconds(timeoutMs), TimeoutStrategy.Optimistic,
(context, timeSpan, task, ex) =>
{
Console.WriteLine($"Task timed out after {timeSpan.TotalSeconds} seconds");
return Task.CompletedTask;
});
await retryPolicy.ExecuteAsync(async (ct) =>
{
await client.StartAsync(ct);
}, CancellationToken.None);
// changed the method to accept CancellationToken
public async Task StartAsync(CancellationToken ct = default)
{
await _webSocket.ConnectAsync(_uri, ct).ConfigureAwait(false);
...
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", ct).ConfigureAwait(false);
}
您的 TimeoutAfter
扩展方法被应用了两次,分别用于 ConnectAsync
和 CloseAsync
方法。这意味着每个方法调用都有一个“本地”超时。
在您的 Polly 版本中,您定义了一个“全局”/“总体”超时,其中包括从 ConnectAsync
到 CloseAsync
的所有内容。
因此,这两个版本不相同。
顺便说一句,你的代码有一些问题:
TimeoutAsync
不能有Handle
子句retryPolicy
未在此范围内定义
您可以将 TimeoutAfter
代码替换为此代码以利用 Polly:
public static async Task TimeoutAfter(Func<CancellationToken, Task> task, CancellationToken token, TimeSpan timeout)
{
try
{
await CreateTimeoutConstraint(timeout)
.ExecuteAsync(async (ct) => await task(ct), token)
.ConfigureAwait(false);
}
catch (TimeoutRejectedException ex)
{
throw new OperationCanceledException($"Task timed out after {timeout}", ex);
}
}
private static IAsyncPolicy CreateTimeoutConstraint(TimeSpan threshold)
=> Policy.TimeoutAsync(threshold, TimeoutStrategy.Optimistic,
(context, timeSpan, task, ex) =>
{
Console.WriteLine($"Task timed out after {timeSpan.TotalSeconds} seconds");
return Task.CompletedTask;
});
- 为了尊重用户取消令牌和超时取消令牌,您必须更改签名
- 该方法应该接收一个函数,该函数预期
CancellationToken
和 returns 一个Task
- 它还应该收到用户取消令牌
- 该方法应该接收一个函数,该函数预期
- 在
ExecuteAsync
中调用您“组合”TimeoutPolicy
和用户取消令牌- 如果
token
先请求取消,那么ExecuteAsync
将抛出一个TaskCanceledException
- 如果 TimeoutPolicy 首先请求取消,那么
ExecuteAsync
将抛出一个TimeoutRejectedException
,它被转换为一个OperationCanceledException
- 如果
这个方法的用法是这样的:
await TimeoutAfter((token) => _webSocket.ConnectAsync(_uri, token), ct, OpenTimeoutMs);
...
await TimeoutAfter((token) => _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", token), ct, CloseTimeoutMs);