I have an web api on dotnet core 3.1 and I want to set different timeout specific controller action.I try to create an actionfilter something like below
public class TimeOutAttribute : ActionFilterAttribute
{
private readonly int _timeout;
public TimeOutAttribute(int timeout)
{
_timeout = timeout;
}
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
try
{
var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(_timeout));
await Task.Run(async () => await next(), cts.Token);
}
catch (TaskCanceledException)
{
var request = context.HttpContext.Request;
var message = $"Action exceeded the set timeout limit {_timeout} milisecond for {request.PathBase}{request.Path}";
throw new ActionTimeOutException(message);
}
}
}
and I use it on controller method
[TimeOut(100)]
public async Task<IActionResult> Get()
{
}
Although Get method takes more than 100 ms I can not get exception.Could you see any problem on code or If you have a another options for controller timeout Im ready to try it
Could you see any problem on code
Yes; passing a cancellation token to Task.Run
isn't going to work. The token for that method only cancels the scheduling of the task to the thread pool, not the delegate itself.
The only way to cancel your delegate code is to have your delegate take a CancellationToken
and observe that (usually by passing it to other methods). I have a blog post series on the subject of cancellation.
If you have a another options for controller timeout Im ready to try it
So, that's a harder problem.
There is built-in support for CancellationToken
in ASP.NET Core; you can add a CancellationToken
argument to any controller action method. However, this token doesn't have anything to do with timeouts; it cancels if the user request is aborted (e.g., the user closes their browser).
One approach is to add a CancellationToken
parameter to your action method and have your filter modify the model binding results, replacing the provided CancellationToken
. Something like this should work:
public sealed class TimeoutAttribute : ActionFilterAttribute
{
private readonly TimeSpan _timeout;
public TimeoutAttribute(int timeoutMilliseconds) => _timeout = TimeSpan.FromMilliseconds(timeoutMilliseconds);
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
// Find the CancellationToken argument passed to the action
var cancellationTokenArgument = context.ActionArguments.FirstOrDefault(x => x.Value is CancellationToken);
if (cancellationTokenArgument.Key == null || cancellationTokenArgument.Value == null)
throw new InvalidOperationException("TimeoutAttribute must be used on an action with a CancellationToken");
// Create a new CancellationToken that will be cancelled if *either* the user disconnects *or* a timeout
using var cts = CancellationTokenSource.CreateLinkedTokenSource((CancellationToken)cancellationTokenArgument.Value);
cts.CancelAfter(_timeout);
// Replace the action's CancellationToken argument with our own
context.ActionArguments[cancellationTokenArgument.Key] = cts.Token;
await next();
}
}
This will work - to an extent. Your host (i.e., IIS) likely has its own timeout, and this timeout is completely separate from that one.