Mvc: CancellationToken

Created on 6 Sep 2016  路  27Comments  路  Source: aspnet/Mvc

Sorry if this has been asked before but i cant find any documentation on it. Is it possible in MVC6 to get a CancellationToken triggered when aborting an ajax request for example. IsCancellationRequested is always false. From what i understand this was possible in MVC4 and WebAPI, not MVC 5+6?

``` C#
public async Task CancelTest(int id, CancellationToken cToken)
{
Debug.WriteLine("Started");

for(var i = 1; i <= 10; i++)
{
    await Task.Delay(3000, cToken);

    if (cToken.IsCancellationRequested)
    {
        Debug.WriteLine("Canceled");
        break;
    }
}

return Json(new { data = "CancellationTest", id = id });

}
```

Most helpful comment

Just to clarify. I understood that now CancellationToken is not triggered by a client. But is there any other way to be notified that the client aborted the connection? In my application this is a desired behavior to abandon any further server-side processing when a request is aborted.

All 27 comments

Use the httpContext.RequestAborted token?

Thank you. Same behaviour. I believe if you put a CancellationToken in your action method the model binder binds it to HttpContext.RequestAborted though.

some background here..

/cc @Tratcher in case he wants to add more

The RequestAborted token is triggered server-side, by your code. It's intended to be used when the server wants to cancel pending work based on a timeout or some other criteria.

In old webapi, we used to provide a token that fired when the client disconnected, and this is what we model-bound by default. This led to the assumption that the _right_ thing to do was to always use that token, and pass it into every API call that could accept it.

This led to a lot of exception noise in logs for cases that you really can't troubleshoot.

This also led to the wrong semantics in a lot of cases. Would you abort a database transaction if the client disconnects while it's processing?

This is why we bind RequestAborted and why aborts are now triggered in server-side code rather than by the client hanging up.

Use it in good health with this knowledge.

Thank you for background and clarification. Makes sense to me now.

Just to clarify. I understood that now CancellationToken is not triggered by a client. But is there any other way to be notified that the client aborted the connection? In my application this is a desired behavior to abandon any further server-side processing when a request is aborted.

/cc @Tratcher @davidfowl - where did we put the client disconnected token?

@rynowak RequestAborted is ClientDisconnected, we haven't made any effort to separate them. That said, client disconnects can be difficult to detect. If the client disconnects gracefully (FIN), Kestrel won't detect it unless you send multiple writes and the client sends a RST in response. If the client disconnects with a RST first Kestrel should detect that and fire RequestAborted. WebListener currently triggers RequestAborted for FIN or RST.

So is all the information that I've ever given out on this topic false then?

@Tratcher We should do the same in Kestrel (i.e. fire the RequestAborted token for both FIN and RST). It's affecting SignalR right now.

The order the RST comes in is irrelevant. If a RST is received with or without a prior FIN, RequestAborted will fire. RequestAborted will not fire with just a FIN from the client since the TCP connection is in a half-open CLOSE_WAIT state (i.e. it's still valid to write to the response). Maybe for HTTP we should treat CLOSE_WAIT as disconnected/aborted, but we should investigate to make sure this wouldn't break real clients.

Is there any update on this issue? This is really important especially when you are dealing with disaster recovery situations where the majority of the request should not be processed (due to client disconnection). We have a microservice archuiteture and this can actually prevent a "snowball" effect.

We are currently looking at changing the RequestAborted token for the next release. There's an open PR for this change at https://github.com/aspnet/KestrelHttpServer/pull/1218.

The RequestAborted token will still fire today given failed writes or a RST from the client. In some scenarios, it's possible to send keep alive messages to the client and check the RequestAborted token to more reliably ensure the connection is still alive.

This was fixed in Kestrel.

This definately used to work as I tried it when David Paquette wrote a blog post blog post about it. What release was this fixed in, I'm trying the following code with ASP.NET Core 1.1.1 and can't get this to work as expected.

public class Model { public class CancellationToken { get; set; }

[HttpGet("foo")]
public async Task<IActionResult> Foo(Model model, CancellationToken cancellationToken)
{
    await Task.Delay(10000);
    var a = cancellationToken.IsCancellationRequested;
    var b = this.HttpContext.RequestAborted.IsCancellationRequested;
    var c = model.CancellationToken.IsCancellationRequested;
    return this.Ok(); // Set breakpoint here
}

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script>
var request = $.get(
  "http://localhost:5000/Bar/foo",
  function(msg) {
    alert("done");
  });
setTimeout(function() {
  request.abort();
  alert('aborted');
}, 
1000);
</script>

@RehanSaeed - can you provide more information about what you mean by used to work? How do you expect the system to behave vs what is it doing?

"In MVC 6, uses RequestAborted which from the HttpContext which a CancellationToken so it does work as expected."
David Paquette

However, my code above does not show true for this.HttpContext.RequestAborted.IsCancellationRequested using 1.1.1.

This only works in 2.0 not in 1.x

Hello,
Please clarify: is it true that cancel a ajax call to a async action in ASP.NET Core is not possible in Version 1.x?

robert

It really has nothing to do with Ajax, there's currently no cancellation token that represents the lifetime of the client. If you want to cancel some sever side operation then you need to create a cancellation token with a timeout and pass that to whatever APIs you call.

Hi David,

I have the following async Action in my ASP.NET MVC Controller (see below) where I get some SQL as a parameter and execute that query and the user should have the possibility to press a button to cancel the running query if it takes to Long...

can you give me a short example how to cancel the async Action from the client if I press a button please?

public async System.Threading.Tasks.Task<ActionResult> ReadAsync(string queryJson, string optionsJson, [DataSourceRequest] DataSourceRequest request, CancellationToken cancelToken)
        {
            try
            {

                var query = eqService.GetQueryByJsonDict(queryJson.ToJsonDict());
                var sql = eqService.BuildQuery(query, optionsJson.ToJsonDict());

                var result = await Db.QueryAsync<dynamic>(sql, commandType: System.Data.CommandType.Text,cancellationToken: cts.Token);

                return Json(result.ToDataSourceResult(request));
            }
            catch (Exception ex)
            {
                return Json(ex);
            }

        }

I have just installed VS2017 15.3 and the CORE 2.0 SDK. Created a new ASP.NET CORE 2.0 (CORE) APP, and implemented the CancellationToken in a method, and it still doesn't get triggered. I can even close the browser (Edge), and even then, the HttpContext.RequestAborted (CancellationToken) is not triggered. I've tried with IE, Chrome and Edge, no luck so far!? Any ideas?

## "EDIT!!" - I just figured out, that it actually works with the KESTREL server!
So, this is just an IIS Express issue!

C# public async Task<IActionResult> About(CancellationToken token) { try { ViewData["Message"] = "Your application description page."; DateTime time = DateTime.Now; await Task.Run(() => { double s = -1; while ((DateTime.Now - time).TotalSeconds < 10) { if (HttpContext.RequestAborted.IsCancellationRequested) break; // NEVER GETS HIT! token.ThrowIfCancellationRequested(); if (s != DateTime.Now.Second) { System.Diagnostics.Debug.WriteLine(DateTime.Now.ToString("HH:mm:ss") + " task running...."); s = DateTime.Now.Second; } } }, token); System.Diagnostics.Debug.Print("Task done"); // GETS HIT, EVEN WHEN EDGE IS CLOSED 1 SECOND AFTER THE REQUEST STARTED } catch (Exception ex) { System.Diagnostics.Debug.Print(ex.Message); // NEVER GETS HIT } return View(); }

Ah! You're using IIS right? There's still an issue there https://github.com/aspnet/AspNetCoreModule/issues/38 (unfortunately)

Exactly... yes, i was using IIS Express, and yes, it works like a charm with the Kestrel server :)

I am using kestrel and the example code above does not work.

Seems FIDDLER was my problem (Im on Win10), not sure what it's doing in the background to mess with this but once it was off, i now get TaskCanceledException, which is just fine.

Thanks for contacting us. We believe that the question you've raised have been answered. If you still feel a need to continue the discussion, feel free to reopen it and add your comments.

Was this page helpful?
0 / 5 - 0 ratings