5

I'm need to cancel an API call that returns a Task, but it doesn't take a CancellationToken as a parameter, and I can't add one.

How can I cancel that Task?

In this particular case, I'm using Xamarin.Forms with the Geocoder object.

 IEnumerable<Position> positions = await geo.GetPositionsForAddressAsync(address); 

That call can sometimes take a very long time. For some of my use cases, the user can just navigate to another screen, and that result of that task is no longer needed.

I also worry about my app going to sleep and not having this long running task stopped, or of the task completing and having need of code which is no longer valid.

TimF
  • 151
  • 1
  • 2
  • 8
  • 2
    Assuming that we're talking about .NET code all the way, maybe you could whack it in a child `AppDomain`? Then you can just `Unload()` it if it is misbehaving or being cruel to kittens –  Sep 02 '15 at 03:26
  • @MickyDuncan Unloading a AppDomain is still not the best solution, If you take a unmanaged resource and it does not get released due to the unloading it won't be released until all AppDomains are unloaded and the process terminates. It would likely be better to make it a fully separate process and use some form of IPC to communicate with the two. – Scott Chamberlain Sep 02 '15 at 04:19
  • @ScottChamberlain I did say _"Assuming that we're talking about .NET code all the way"_. Yes, I like the IPC approach thanks Scott –  Sep 02 '15 at 04:21
  • 1
    @mickyDuncan I would consider performing a `Marshal.AllocHGlobal` with a `Marshal.StructureToPtr` [to serialize a struct](http://stackoverflow.com/a/28761634/80274) .NET code, but I see your point. – Scott Chamberlain Sep 02 '15 at 04:32

2 Answers2

9

The best that I have read about doing this is from Stephen Toub on the "Parallel Programming with .NET" blog.

Basically you create your own cancellation 'overload':

public static async Task<T> WithCancellation<T>( 
    this Task<T> task, CancellationToken cancellationToken) 
{ 
    var tcs = new TaskCompletionSource<bool>(); 
    using(cancellationToken.Register( 
                s => ((TaskCompletionSource<bool>)s).TrySetResult(true), tcs)) 
        if (task != await Task.WhenAny(task, tcs.Task)) 
            throw new OperationCanceledException(cancellationToken); 
    return await task; 
}

And then use that with a try/catch to call your async function:

try 
{ 
    await op.WithCancellation(token); 
} 
catch(OperationCanceledException) 
{ 
    op.ContinueWith(t => /* handle eventual completion */); 
    … // whatever you want to do in the case of cancellation 
}

Really need to read his blog posting...

SushiHangover
  • 73,120
  • 10
  • 106
  • 165
  • 4
    Just to be clear, that doesn't cancel the task... it cancel's the await on the task, and then you queue up a "cleanup" action, for whenever it does eventually complete. Neat concept ,and interesting blog post. – b0redom Sep 02 '15 at 04:55
  • This is great. I can extend this to work with timeouts as well. I can handle the actual ending of the task in its own thread, and just discard the data. Thanks! – TimF Sep 02 '15 at 17:40
4

In short... No its not possible to cancel a c# Task that doesn't attempt to observe a CancellationToken, at least not in the context you are referring to. (ending the app for example, would do it)

The link here on Cancellation discusses the various patterns on how to leverage a cancellation token but since you are dealing with a library you don't control, there isn't much you can do.

b0redom
  • 387
  • 2
  • 11