I set up a CancellationTokenSource with handler
public class AppTimeout
{
public async Task Invoke(HttpContext httpContext)
{
var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(httpContext.RequestAborted);
cancellationTokenSource.CancelAfter(myTimestamp);
cancellationTokenSource.Token.Register(() =>
{
log.info("...");
});
await _next(httpContext);
}
}
My problem is if I have only one request in timeout , the callback of cancellationTokenSource.Token is called for all request that have been processed by Invoke methode, even request that already finished in correct time
Do you know why I encounter this behaviour and how to fix it please?
using var registration = timeoutCancellationTokenSource.Token.Register(() => {
log.info($"timeout path is {path}");
});
// your other code here...
Now it will unregister correctly when complete, i.e. when leaving the scope of the using.
Related
[ApiController]
[Route("[controller]")]
public class JobController : ControllerBase
{
private readonly IEventBus _bus;
public JobController(IUnitOfWork unitOfWork, IEventBus bus)
{
_bus = bus;
}
...
[HttpPost]
public async Task<IActionResult> Post([FromBody]JobRequest request)
{
try
{
var command = new JobCommand{ Id = 1, Name = "abc"};
await _bus.SendCommand(command);
}
catch (OperationCanceledException)
{
_logger.LogInformation("Task was cancelled!");
}
return CreatedAtAction(nameof(GetById), new { id = 0 }, null);
}
}
public class JobCommandHandler : IRequestHandler<JobCommand, bool>
{
private readonly ILogger<JobCommandHandler> _logger;
public JobCommandHandler(ILogger<JobCommandHandler> logger)
{
_logger = logger;
}
public async Task<bool> Handle(JobCommand request, CancellationToken cancellationToken)
{
//
// I was able to reproduce manual cancellation by using this code below
var cts = new CancellationTokenSource();
cts.Cancel();
cancellationToken = cts.Token;
// how this can be populated sent from the place where I'm issuing command?
cancellationToken.ThrowIfCancellationRequested();
...
// long running task
}
}
My question is:
Do I need to send the CancellationTokenSource together with command? If so, how to trigger that from the swagger Cancel button and is it good practice to include CancellationTokenSource
to be the property of CommandBase class which every Command will extend?
If you want to cancel all IO and processing if the Http Request which initiated everything got canceled, then, yes. You have to make everything in its path cancellation aware and pass the token through all layers.
You can get a request cancellation token from the HttpContext in the controller from HttpContext.RequestAborted.
Now, in your example, I'm not sure what IEventBus is. If that is some kind of distributed messaging backend, simply passing a CancellationToken will not work, in that case you could send a cancel event to cancel the action I guess.
If it is in-memory eventing only, then it should probably work.
Please help me to understand why this code cause a deadlock?
I have an asp.net web api application and I tried to make some controller method asynchronous.
[HttpPost]
[Authentication]
public async Task<SomeDTO> PostSomething([FromBody] SomeDTO someDTO)
{
return await _service.DoSomething(someDTO);
}
this is how looks the called service method:
public async Task<SomeDTO> DoSomething(SomeDTO someDTO)
{
...
var someTask = Task.Run(() =>
{
var entity = new SomeEntity(someDTO);
return _repository.Create(entity);
});
...
var result = await someTask;
...
}
And there is some globalhandler, that prints a response to a console.
public class AppGlobalHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var resp = base.SendAsync(request, cancellationToken);
Debug.WriteLine($"Response:{request.RequestUri}{Environment.NewLine}{resp?.ConfigureAwait(false).GetAwaiter().GetResult()?.Content?.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult()}");
return resp;
}
}
Looks like ConfigureAwait(false).GetAwaiter().GetResult()
blocks the caller thread, but I supposed that ConfigureAwait(false) should avoid this, isn't it?
ConfigureAwait(false) would not help you here because it must be all the way down in the call stack (see more here) not at place where you wait synchronously, i.e. it depends rather on the implementation of base.SendAsync. If it acquired a lock on current thread it's too late to do something about it. It is also not recommended in ASP.net pipeline to continue responding on other thread after all (see discussion here and post here).
Finally it is always a highly risky idea to wait synchronously in async context.
If you need to read content, why not doing it like that:
public class AppGlobalHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var resp = await base.SendAsync(request, cancellationToken);
var content = resp?.Content != null
? (await resp.Content.ReadAsStringAsync())
: string.Empty;
Debug.WriteLine($"Response:{request.RequestUri}{Environment.NewLine}{content}");
return resp;
}
}
I think you overlook async keyword in Task.Run() method.
public async Task<SomeDTO> DoSomething(SomeDTO someDTO)
{
var someTask = Task.Run( async () => //simply add this for async run
{
var entity = new SomeEntity(someDTO);
return _repository.Create(entity);
});
var result = await someTask;
}
I using some third-party class that have a long time work call DoLongWork().
When the user want to stop the "DoLongWork" we need to call the method StopWork().
When the DoLongWork method is working the UI need to show some loading bar.
I want to create a third-party proxy class.
In this class I will create a method called StartWork - this method will return Task and when the user is cancel the task using CancellationToken two actions will made:
1) the third-party method "StopWork" will called
2) the UI will stop the loading bar.
I try this but there is some problems catching the cancellation in the third-party proxy class and bubbling the cancellation to the ViewModel class.
public class MyViewModel
{
private CancellationTokenSource _cancellationTokenSource;
private ThirdPartyServiceProxy _thirdPartyServiceProxy = new ThirdPartyServiceProxy();
public bool IsUIInLoadingMode { get; set; }
public async void Start()
{
try
{
_cancellationTokenSource = new CancellationTokenSource();
IsUIInLoadingMode = true;
await _thirdPartyServiceProxy.StartWork(_cancellationTokenSource.Token);
_cancellationTokenSource = null;
}
catch (OperationCanceledException)/*Issue - This never called*/
{
IsUIInLoadingMode = false;
}
}
public void Stop()
{
_cancellationTokenSource?.Cancel();
}
}
public class ThirdPartyServiceProxy
{
private ThirdPartyService _thirdPartyService = new ThirdPartyService();
public Task StartWork(CancellationToken token)
{
var task = Task.Factory.StartNew(() =>
{
_thirdPartyService.DoLongWork();
},token);
//?? - Handle when task canceld - call _thirdPartyService.StopWork();
return task;
}
}
There's a couple of common ways to observe cancellation tokens: periodic polling with ThrowIfCancellationRequested and registering callbacks with Register.
In this case, polling isn't possible, since you don't control the code in DoLongWork. So you'll have to register a callback, which is more work.
public void DoWork(CancellationToken token)
{
token.ThrowIfCancellationRequested();
using (token.Register(() => _thirdPartyService.StopWork()))
_thirdPartyService.DoLongWork();
}
This wrapper assumes that DoLongWork will throw OperationCanceledException if canceled by StopWork.
The wrapper can then be invoked as:
await Task.Run(() => _thirdPartyServiceProxy.StartWork(_cancellationTokenSource.Token));
As a side note, I have switched to Task.Run; this is because StartNew is dangerous.
Given the following code:
public async Task Send() // part of Sender class
{
// sync code
}
// //
private async Task HandleMessage()
{
// await sender.Send(); // exits HandleMessage immediately
sender.Send().Wait(); // works as expected, waiting to complete
DoOtherStuff(); // doesn't get hit with await
return;
}
RunRecurringTask(async () => await HandleMessage(), result);
public void RunRecurringTask(Action action, RecurringTaskRunResult result)
{
action();
result.DoStuff();
}
I thought that await tells the thread to come back when the awaited thing is complete, but it looks like for some reason that's not happening: the remaining code is never hit and everything just... stops. What could be causing this?
This is a console application in an Azure WebJob, for what it's worth. When Wait is used, I get the expected results, however with await, the job just completes.
You should never do async void unless you are writing a event handler. A Action with the async modifier is a async void method. You need to make the argument a Func<Task> and then do await action() in your RunRecurringTask
private async Task HandleMessage()
{
await sender.Send();
DoOtherStuff();
return;
}
RunRecurringTask(async () => await HandleMessage(), result);
//You also could do
//RunRecurringTask(() => HandleMessage(), result);
public async Task RunRecurringTask(Func<Task> action, RecurringTaskRunResult result)
{
await action();
result.DoStuff();
}
If you had other methods that where not marked with async you will need to change all of them up the call stack till you get to the entry point from the SDK. The SDK understands how to handle functions with a async Task return type since the 0.4.0-beta version.
Is there a way to figure out in ASP.NET Web API beta whether the HTTP request was cancelled (aborted by user of for any another reason)? I'm looking for opportunity to have a kind of cancellation token out-of-the-box that will signal that the request is aborted and therefore long-running ops should be aborted as well.
Possible related question - the use case for the CancellationTokenModelBinder class. What's the reason to have a separate binder for cancellation token?
You could check Response.IsClientConnected from time to time to see if the browser is still connected to the server.
I'd like to sum-up a bit. The only approach that seem to work is checking Response.IsClientConnected.
Here some technical details regarding what is going behind the stage:
here and here
This approach has some flaws:
Works only under IIS (no self-hosting, no Dev Server);
According to some SO answers may be slow (do not react immediately after client disconnected): here;
There are considerations regarding this call cost: here
At the end I came up with the following piece of code to inject CancellationToken based on IsClientConnected into the Web API controller:
public class ConnectionAbortTokenAttribute : System.Web.Http.Filters.ActionFilterAttribute
{
private readonly string _paramName;
private Timer _timer;
private CancellationTokenSource _tokenSource;
private CancellationToken _token;
public ConnectionAbortTokenAttribute(string paramName)
{
_paramName = paramName;
}
public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
{
object value;
if (!actionContext.ActionArguments.TryGetValue(_paramName, out value))
{
// no args with defined name found
base.OnActionExecuting(actionContext);
return;
}
var context = HttpContext.Current;
if (context == null)
{
// consider the self-hosting case (?)
base.OnActionExecuting(actionContext);
return;
}
_tokenSource = new CancellationTokenSource();
_token = _tokenSource.Token;
// inject
actionContext.ActionArguments[_paramName] = _token;
// stop timer on client disconnect
_token.Register(() => _timer.Dispose());
_timer = new Timer
(
state =>
{
if (!context.Response.IsClientConnected)
{
_tokenSource.Cancel();
}
}, null, 0, 1000 // check each second. Opts: make configurable; increase/decrease.
);
base.OnActionExecuting(actionContext);
}
/*
* Is this guaranteed to be called?
*
*
*/
public override void OnActionExecuted(System.Web.Http.Filters.HttpActionExecutedContext actionExecutedContext)
{
if(_timer != null)
_timer.Dispose();
if(_tokenSource != null)
_tokenSource.Dispose();
base.OnActionExecuted(actionExecutedContext);
}
}
If you added CancellationToken in to controller methods, it will be automatically injected by the framework, and when a client calls xhr.abort() the token will be automatically cancelled
Something similar to
public Task<string> Get(CancellationToken cancellationToken = default(CancellationToken))
For MVC you can also refer to
HttpContext.Current.Response.IsClientConnected
HttpContext.Response.ClientDisconnectedToken
For .NetCore
services.AddTransient<ICustomInterface>(provider => {
var accessor = provider.GetService<IHttpContextAccessor>);
accessor.HttpContext.RequestAborted;
});