5

I want to be able to cancel reading from a database or http request instantly when our app is reading some data from either and the user hits the cancel button. Neither call is immediately cancellable, even for the async versions of the calls.

What if I set the code to end the task as soon as the next database/http call completes? And then forget about that task and continue on in my app? The one downside I can see is if the task does not complete before the user exists the app.

Update: JPVenson provided a great solution for doing this right for http and ADO.NET. However, we also at times do this making calles to the Salesforce API which as best as I can figure out, has no cancel mechanism. So the problem remains for that case.

On a cancel I don't need what is being returned so from my apps point of view, I'm fine forgetting about the task. Is there any down side to doing this?

David Thielen
  • 28,723
  • 34
  • 119
  • 193
  • Are you saying that even if you send in a cancellation token and cancel that it is not immediate? – Magnus Jan 01 '20 at 13:28

2 Answers2

1

how would you "kill" a task? AFAIK you can only "kill" Threads but not tasks as they have no such mechanism.

When using the Standard TaskScheduler, Tasks will be enqueued as an ThreadPoolItem and in the case that you stop your application, all thoese ThreadPool Thread will be killed including all remanding/still running Tasks.

For the Headline: It truly depends on the kind of Task but in general you always want to observe the result of any of your Tasks for at least Logging purposes. If you do not observe and also handle potential exceptions that are thrown by the task, you risk a "corrupted state" of your application where the task might crash in a way you did not anticipate before and therefore altering data within your app that does not make sense.

So yes, it is highly recommended that you handle all of your tasks at very least for logging and error correction/observation.

For Canceling an HTTP call: WebClient: how to cancel a long running HTTP request?

For Canceling an SQL Command: This is a way other story and heavily depends on the ORM you are using - EF Cancelling an Entity Framework Query - Ado.Net https://learn.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqlcommand.cancel?view=netframework-4.8

Venson
  • 1,772
  • 17
  • 37
  • First off, thank you - great answer. Second, while that's very helpful on cancelling http & ADO.NET queries, we also have times where's it's queries to the Salesforce API and that does not have a cancel. So for that case, can we abandon the task? – David Thielen Jan 01 '20 at 16:29
  • 1
    You could just not call "Result" nor "Wait()" on that task which "abandons" the task, simply you just forget that it exists, but i would strongly advice against it. That would be the easiest part, but "stopping" a task is not possible. Its just to designed for it, you must think of an Task as an more separate delegate in .net, there is no way in "hard stopping" an delegate from outside. In the end, at least log if the task failed in the end because if you are running into (for example) throttling because of to many calls and you look into your log and see only non canceled tasks, would be bad. – Venson Jan 02 '20 at 02:11
  • 1
    @DavidThielen i saw your update. Thanks for the mention. How to you call the Salesforce api? i made some small Research and found only examples that are using HttpClients. – Venson Jan 02 '20 at 02:16
  • I call Salesforce using SforceService, specifically the method describeSObjects() – David Thielen Jan 02 '20 at 11:48
  • @DavidThielen i see. In that case i would propose you to switch to a Http based framework for Salesforce because SforceService builds upon WCF and that makes it complicated. – Venson Jan 02 '20 at 16:17
  • Unfortunately switching would be a royal PITA - this is all in a library that is used a lot of different ways and the QA hit on that change would be gigantic. So gotta live with the world as it is. – David Thielen Jan 02 '20 at 16:26
  • @DavidThielen thought so. In Conclusion: Abandoning a Task is a decision that should be very carefully considered and is most times not to advised. Tasks cannot be canceled only threads can be and its also not advised to kill threads as the result to your state of application can be unpredictable. The worst case would be to start several Tasks, forgetting about them and then try to access shared resources with that Task/external resource. This would be undebuggable and lead to a lot of hard to reproduce bugs. – Venson Jan 02 '20 at 16:39
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/205244/discussion-between-venson-and-david-thielen). – Venson Jan 02 '20 at 16:42
0

In my understanding the issue is more about not being able to cancel a task launched by a third-party library, ie Salesforce library.

If the 3-rd party library do not expose such a feature, you can still choose to forget or cancel, but you will not be sure it terminates gracefully.

  • Run it in a task (a pooled thread) and you can only control not to wait for its result.

  • Run it in a thread, or another process, and you can control when it terminates (ie the user clicks on a button).

In all cases you can't control how it terminates, especially make sure it releases the allocated ressources, closes connection, and other things called disposing objects in .NET world.

That being said, you can run it in a task that returns when one of the following is true:

  • an API call has returned

  • the user click on a Cancel button

The first solution which come to my mind would be a loop like

// ...

while(true)
{
    Task waitForUserCancellation = Task.Delay(300);

    Task winner = await (Task.WhenAny (apiCallOne, apiCallTwo, waitForUserCancellation));

    if (winner == waitForUserCancellation)
    {
        cancellationToken.ThrowIfCancellationRequested(); // for example; it's not the only one possible way 
        // but if the exception is thrown, you should then call .Cancel() on tasks apiCallOne and apiCallTwo that offer this feature
    }
    else
    {
        // handle a result from one of the API with winner object
    }
}

The cancellationToken is passed by parameter to the method.

It is simply the field Token of an object var cancellationSource = new CancellationTokenSource();, that is known by the user interface layer. And when the button handler is triggered, the method cancellationSource.Cancel() is called.

Guillaume S.
  • 1,515
  • 1
  • 8
  • 21