23

I have the following two applications

  • Angular 6/7 App
  • .Net Core Web API

I am making GET request to API using Angular's HttpClient as shown below

this.subscription = this.httpClient.get('api/Controller/LongRunningProcess')
                                   .subscribe((response) => 
                                   {
                                      // Handling response
                                   });

API controller's LongRunningProcess method has the following code

    [HttpGet]
    [Route("LongRunningProcess")]
    public async Task<IActionResult> LongRunningProcess(CancellationToken cancellationToken)
    {
        try
        {
            // Dummy long operation
            await Task.Factory.StartNew(() =>
            {
                for (int i = 0; i < 10; i++)
                {
                    // Option 1 (Not working)
                    if (cancellationToken.IsCancellationRequested)
                        break;

                    // Option 2 (Not working)
                    cancellationToken.ThrowIfCancellationRequested();

                    Thread.Sleep(6000);
                }

            }, cancellationToken);
        }
        catch (OperationCanceledException e)
        {
            Console.WriteLine($"{nameof(OperationCanceledException)} thrown with message: {e.Message}");
        }

        return Ok();
    }

Now I want to cancel this long-running process so I am unsubscribing from client side as shown below

// On cancel button's click
this.subscription.unsubscribe();

Above code will cancel the request and I can see it is canceled in the Network tab of the browser as shown below

enter image description here

But it is not going to make IsCancellationRequested to true in the method LongRunningProcess of the API, so the operation will keep going.

[Note]: Both Option 1 and Option 2 in API method are not working even if I make a call using postman.

Question: Is there any way to cancel that LongRunningProcess method's operation?

Advait Baxi
  • 1,530
  • 14
  • 15
  • May be there is an issue while using both Kestrel and IIS Express. I tried without using IIS Express (refer https://stackoverflow.com/questions/39574061/httprequest-not-aborted-cancelled-on-browser-abort-in-asp-net-core-mvc and https://stackoverflow.com/questions/47153525/whats-the-difference-between-httpcontext-requestaborted-and-cancellationtoken-p) and it worked perfectly as expected. I got IsCancellationRequested = true. – Advait Baxi Sep 02 '19 at 06:13
  • Any answer to this question? I am having the same issue. – Maurizio In denmark Aug 16 '21 at 11:46
  • I didn't check later on but if you read my previous comment that may help – Advait Baxi Aug 16 '21 at 12:16

6 Answers6

1

When angular cancel request, you can get cancellation token from http context

   CancellationToken cancellationToken = HttpContext.RequestAborted;
    if (cancellationToken.IsCancellationRequested)
    {
        // The client has aborted the request
    }
divyang4481
  • 1,584
  • 16
  • 32
0

You dont need break in this case only use like this

 [HttpGet]
 [Route("LongRunningProcess")]
 public async Task<IActionResult> LongRunningProcess(CancellationToken cancellationToken)
 {
   for (int i = 0; i < 10; i++)
   {
      cancellationToken.ThrowIfCancellationRequested();
       // Dummy long operation
       await Task.Factory.StartNew(() => Thread.Sleep(60000));
   }

    return Ok();
 }

You can read it more here

Tony Ngo
  • 19,166
  • 4
  • 38
  • 60
  • Thanks for your response. I already went through the above blog and it also states that 'Instead the Task.Delay call throws a TaskCancelledException when it detects that the CancellationToken.IsCancellationRequested property is true, immediately halting execution.' and even Microsoft docs say that method 'ThrowIfCancellationRequested' provides functionality equivalent to if (token.IsCancellationRequested)throw new OperationCanceledException(token);. But the issue is cancellation token is not able to detect that the request is canceled. – Advait Baxi Sep 01 '19 at 15:27
  • I will try again and will get back to you on this. – Advait Baxi Sep 01 '19 at 15:33
0

This is because your dummy long operation does not monitor the canncellationToken. I'm not sure it is actually your intention to start 10 one-minute tasks all in parallel without any delay, which is what your code does.

In order to have a dummy long operation, the code would be like

[HttpGet]
[Route("LongRunningProcess")]
public async Task<IActionResult> LongRunningProcess(CancellationToken cancellationToken)
{
    // Dummy long operation
    await Task.Run(() =>
        {
            for (var i = 0; i < 60; i++)
            {
                if (cancel.IsCancellationRequested)
                    break;
                Task.Delay(1000).Wait();
            }
        });

    return Ok();
}

Task.Run is just equivalent to Task.Factory.StartNew, by the way.

However, if you just need a dummy long-run operation in your web API, then you can also simply use Task.Delay, which supports cancellation token. Task.Delay throws an exception when the request is canceled, so add exception handling code when you need to do something after request cancellation.

[HttpGet]
[Route("LongRunningProcess")]
public async Task<IActionResult> LongRunningProcess(CancellationToken cancellationToken)
{
    // Dummy long operation
    await Task.Delay(60000, cancel);

    return Ok();
}
Yas Ikeda
  • 973
  • 1
  • 9
  • 16
  • Thanks for your response. Here in the question, I have not written code to pass cancellation token in any task. But in actual code I did and I put a breakpoint in a loop and then canceled the request from client-side and checked it. It didn't make 'IsCancellationRequested' to 'true'. Still, I will try again and get back to you. – Advait Baxi Sep 01 '19 at 15:47
  • I see, then the problem must be in your Angular code where you call `this.subscription.unsubscribe()`. how about attaching the code around that to your question? – Yas Ikeda Sep 01 '19 at 15:53
  • I have already written a piece of code in the question. Only that much code is required to cancel any request as per my knowledge. – Advait Baxi Sep 01 '19 at 15:54
  • as I don't see any wrong in the client code you provided, so I just suspected something that you do in the rest of your client code. there would be something wrong. just as a possibility though – Yas Ikeda Sep 01 '19 at 16:00
  • _Task.Run is just equivalent to Task.Factory.StartNew, by the way._. [almost](https://stackoverflow.com/questions/38423472/what-is-the-difference-between-task-run-and-task-factory-startnew) – Joelius Sep 01 '19 at 16:22
  • @Advait Baxi, there's an interesting issue in angular's github https://github.com/angular/angular/issues/27411#issuecomment-444166771 . don't you have such a problem in your rxjs imports? – Yas Ikeda Sep 01 '19 at 16:25
  • @Joelius the point of my answer is not that part, by the way. the code provided in the question initially had a code where it didn't check the cancellation token inside task run, which is like the code in Tony's answer. the answer is now updated, so it looks fine now. I'm guessing something wrong in client code rather than the server code. – Yas Ikeda Sep 01 '19 at 16:31
  • @YasIkeda Even if I make a call using postman, it didn't work. It didn't update IsCancellationRequested to true. It didn't cancel the task. – Advait Baxi Sep 01 '19 at 16:37
  • I didn't criticize your answer, I just wanted to point out that the calls are not fully equal, mostly because you can't even call them with the same arguments. The cursive part was a citation of your answer. My comment was only about that part :) Let's stop hijacking the comments with this now. – Joelius Sep 01 '19 at 16:38
0

Any http observables still running at the time will complete and run their logic unless you unsubscribe in onDestroy(). Whether the consequences are trivial or not will depend upon what you do in the subscribe handler. If you try to update something that doesn't exist anymore you may get an error.

Tip: The Subscription contains a closed boolean property that may be useful in advanced cases. For HTTP this will be set when it completes. In Angular it might be useful in some situations to set a _isDestroyed property in ngDestroy which can be checked by your subscribe handler.

Tip 2: If handling multiple subscriptions you can create an ad-hoc new Subscription() object and add(...) any other subscriptions to it - so when you unsubscribe from the main one it will unsubscribe all the added subscriptions too.

So, best practice is to use takeUntil() and unsubscribe from http calls when the component is destroyed.

   import { takeUntil } from 'rxjs/operators';
   .....
   ngOnDestroy(): void {
     this.destroy$.next();  // trigger the unsubscribe
     this.destroy$.complete(); // finalize & clean up the subject stream
   }
Moinul Islam
  • 469
  • 2
  • 9
  • 1
    Usually I do in the following way (the same way you are saying) import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; private unsubscriber = new Subject(); ngOnDestroy() { if (this.unsubscriber) { this.unsubscriber.next(); this.unsubscriber.complete(); } } But in the above scenario (in a question) I want to cancel the request on a button so I took a subscription and did unsubscribe on a button's click. – Advait Baxi Sep 01 '19 at 17:00
0
var cancellationToken = new CanellationToken();
    cancellationToken.CancelAfter(2000);
    using (var response = await _httpClient.GetAsync("emp", 
        HttpCompletionOption.ResponseHeadersRead, cancellationTokenSource.Token))
    {
        response.EnsureSuccessStatusCode();
        var stream = await response.Content.ReadAsStreamAsync();
        var emp = await JsonSerializer.DeserializeAsync<List<empDto>>(stream, _options);
    }

Further we can also have this "CancellationToken" class, which is nothing much Http client method which terminates the request after certain time-interval.

Prabhanath
  • 98
  • 1
  • 7
0
    • In angular subscription.unsubscribe(); closes the channel and causes CORE to cancel the API caller's thread, that's good.
    • Don't use await Task.Run(()... This creates a result/task that should be disposed, if not, the task keeps going, your pattern doesn't permit this - that's why it continues to run.
    • Simply - 'await this.YourLongRunningFunction()', I'm pretty sure that when the owning thread throws the OperationCancelled exception your task will end.
  1. If "3" doesn't work, then pass a cancellation token to your long running task and set that when you catch your OperationCancelled exception.
DougS
  • 127
  • 2
  • 6