Ivan-Mark Debono
Ivan-Mark Debono

Reputation: 16280

How to cancel an asynchronous Task?

I have a method that loads data, eg.:

public async GetCustomers()
{
    await Task.Run(() => {
        for (int i = 0; i < 99; i++)
            customers = customerRequest.GetCustomers();
    });
}

customerRequest is a simple class that uses HttpClient to connect to a WebApi server. The call stack is as follows:

method->request->controller action->server layer->repository->db

Currently, the controller action returns IHttpActionResult, whilst the service and repository layers return IEnumerable<Customer>. All methods are called synchronously.

For testing purposes, I added the for loop to increase the task's delay and see the SQL statements being executed.

If the user decides to close the form, the task is still being executed in the background and the SQL statements are still sent to the db.

What is the correct way to cancel such a task?

Upvotes: 1

Views: 3588

Answers (2)

Stephen Cleary
Stephen Cleary

Reputation: 456322

You'd want to use CancellationTokenSource, and pass its CancellationToken into your method. When the form closes, call CancellationTokenSource.Cancel.

Note, however, that you want to pass the CancellationToken into the methods that actually use it. As I describe on my blog, Task.Run is not one of them.

What you really want to do is start at the lowest level (on the client side), and pass the CancellationToken into whatever HttpClient method that you're using (i.e., like this one).

Then work your way up. I'd also recommend making your code asynchronous. When you're done, you should end up with a GetCustomers that looks like this:

public async Task GetCustomersAsync(CancellationToken token)
{
  for (int i = 0; i < 99; i++)
    customers = await customerRequest.GetCustomersAsync(token);
}

If you want to Really Be Sure (tm) that no spurious requests go out, you can also explicitly check the token before doing the request:

public async Task GetCustomersAsync(CancellationToken token)
{
  for (int i = 0; i < 99; i++)
  {
    token.ThrowIfCancellationRequested();
    customers = await customerRequest.GetCustomersAsync(token);
  }
}

You can also handle cancellation on the server side if that's important to you.

Upvotes: 3

ManOVision
ManOVision

Reputation: 1893

Checkout CancellationTokenSource. You can keep one of these long lived and a button or a close event call Cancel() on it. It just needs to be passed to all your tasks if you want them all to cancel. The task will throw an exception so make sure you wrap in a try catch. You can also check if a cancel request has been made within your task to try to break out in a graceful manor.

var cts = new CancellationTokenSource();

await Task.Run(() =>
{
    //do work, will throw if cts.Cancel() is called
}, cts.Token);

//wait 2 seconds then cancel
await Task.Delay(2000);

cts.Cancel();

Upvotes: 1

Related Questions